Consumo eficiente de API com Dio no Flutter
Para pessoas como eu, que se candidataram a milhares de empregos no Flutter, você pode testemunhar que “CONSUMIR APIS REST
” é sempre um requisito nessas funções-chave. Hoje, criaremos um aplicativo simples de comércio eletrônico que exibe a lista de produtos de uma API usando o Dio.
Vamos começar
Vamos replicar esse design….
Para usar o Dio, digite em seu terminal
flutter pub add dio
Crie uma instância dele para referenciá-lo facilmente em qualquer lugar da tela do seu aplicativo
import 'package:dio/dio.dart'; final dio = Dio();
Lembre-se de que tínhamos esta url+endpoint para obter todos os produtos
https://fakestoreapi.com/products
Vamos verificar se funciona
Para fazer isso, crie uma função chamada “fetchProducts
”
void fetchProducts()async{ final response = await dio.get("https://fakestoreapi.com/products") print(response) }
Aqui, criamos uma função simples que busca todos os produtos desse endpoint, mas queremos verificar se ela realmente funciona antes de criar nosso aplicativo. Para isso, teríamos de executar essa função antes de criar toda a árvore de widgets do nosso aplicativo.
@override void initState() { super.initState(); fetchProducts(); }
Portanto, você deve ter uma visualização completa como esta
import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; class TestScreen extends StatefulWidget { const TestScreen({super.key}); @override State<TestScreen> createState() => _TestScreenState(); } class _TestScreenState extends State<TestScreen> { final dio = Dio(); void fetchProducts() async { var result = dio.get('https://fakestoreapi.com/products'); } initState() { fetchProducts(); super.initState(); } @override Widget build(BuildContext context) { return const Scaffold(); } }
Depois de fazer isso, você deverá obter dados JSON em seu console
{ "id": 1, "title": "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops", "price": 109.95, "description": "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday", "category": "men's clothing", "image": "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg", "rating": { "rate": 3.9, "count": 120 } }, { "id": 2, "title": "Mens Casual Premium Slim Fit T-Shirts ", "price": 22.3, "description": "Slim-fitting style, contrast raglan long sleeve, three-button henley placket, light weight & soft fabric for breathable and comfortable wearing. And Solid stitched shirts with round neck made for durability and a great fit for casual fashion wear and diehard baseball fans. The Henley style round neckline includes a three-button placket.", "category": "men's clothing", "image": "https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg", "rating": { "rate": 4.1, "count": 259 } }, { "id": 3, "title": "Mens Cotton Jacket", "price": 55.99, "description": "great outerwear jackets for Spring/Autumn/Winter, suitable for many occasions, such as working, hiking, camping, mountain/rock climbing, cycling, traveling or other outdoors. Good gift choice for you or your family member. A warm hearted love to Father, husband or son in this thanksgiving or Christmas Day.", "category": "men's clothing", "image": "https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg", "rating": { "rate": 4.7, "count": 500 } }, { "id": 4, "title": "Mens Casual Slim Fit", "price": 15.99, "description": "The color could be slightly different between on the screen and in practice. / Please note that body builds vary by person, therefore, detailed size information should be reviewed below on the product description.", "category": "men's clothing", "image": "https://fakestoreapi.com/img/71YXzeOuslL._AC_UY879_.jpg", "rating": { "rate": 2.1, "count": 430 } }, { "id": 5, "title": "John Hardy Women's Legends Naga Gold & Silver Dragon Station Chain Bracelet", "price": 695, "description": "From our Legends Collection, the Naga was inspired by the mythical water dragon that protects the ocean's pearl. Wear facing inward to be bestowed with love and abundance, or outward for protection.", "category": "jewelery", "image": "https://fakestoreapi.com/img/71pWzhdJNwL._AC_UL640_QL65_ML3_.jpg", "rating": { "rate": 4.6, "count": 400 } },
Os dados são mais do que isso. Eu usei isso para mostrar quais deveriam ser os resultados.
Metade do trabalho já foi feito.
Vamos criar a interface do usuário
Você notaria na imagem fornecida que há a imagem do produto, o nome do produto, a etiqueta de preço do produto e, em seguida, a classificação. Você não vai gostar de inseri-los um após o outro. Você gostaria?
Para fazer isso, vamos criar uma classe em outro arquivo dart com todos esses recursos
class Product { final String name; final String price; final String image; Product({required this.name, required this.price, required this.image}); }
Você também notará que os produtos são projetados da mesma forma, mas apenas com textos ou parâmetros diferentes.
Crie outro arquivo chamado “demopage.dart
” que lida com todas essas diferenças na interface do usuário e cole os seguintes códigos
class ProductItem extends StatelessWidget { final Product product; ProductItem({required this.product}); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(10), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Image.asset( product.image, fit: BoxFit.cover, height: 120, width: double.infinity, ), Padding( padding: const EdgeInsets.all(8.0), child: Text( product.name, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), Padding( padding: const EdgeInsets.only(left: 8.0, bottom: 8.0), child: Text( '\$${product.price}', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), Padding( padding: const EdgeInsets.only(left: 8.0, bottom: 8.0), child: Text( 'BUY NOW!', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), ), ], ), ); } }
Em outras palavras, criei um widget sem estado que tem um contêiner. No contêiner, há um widget de coluna que resolve toda a lógica em relação à imagem, ao nome ou ao preço do produto.
Vamos voltar à nossa página TestScreen
Antes de chamar nossa API, vamos colocá-la em um dado de demonstração para ver se tudo funciona corretamente.
import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; class TestScreen extends StatefulWidget { const TestScreen({super.key}); @override State<TestScreen> createState() => _TestScreenState(); } class _TestScreenState extends State<TestScreen> { final dio = Dio(); void fetchProducts() async { var result = dio.get('https://fakestoreapi.com/products'); } initState() { fetchProducts(); super.initState(); } final List<Product> products = [ Product( name: 'Product 1', price: '9.99', image: 'assets/product1.png', ), Product( name: 'Product 2', price: '19.99', image: 'assets/product2.png', ), // Add more products as needed ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('E-commerce App'), ), body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(16.0), child: Text( 'Category 1', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), GridView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, ), itemCount: products.length, itemBuilder: (context, index) { return ProductItem(product: products[index]); }, ), ], ), ); ); } }
Portanto, em termos simples, estamos dizendo que, a partir da classe Product, criamos uma visualização de grade que exibe a lista de produtos.
Funciona!
Agora, vamos trabalhar com nossa API.
Você deve se lembrar que imprimiu dados JSON no console. Seu trabalho como engenheiro do Flutter é garantir que os dados sejam exibidos na UI.
Como você faz isso?
É bem simples.
Lembre-se de que criamos manualmente uma classe Product que tem um nome String, um texto String e uma imagem String.
Além disso, para nossos dados JSON, você também pode criar a classe manualmente, mas para alguém como eu, que gosta de “faaji express(soft life)”, há uma maneira mais rápida de fazer isso.
Copie os dados JSON e cole-os em https://app.quicktype.io/. Ele criará um arquivo de classe para o seu aplicativo. Você terá o seguinte
// To parse this JSON data, do // // final products = productsFromJson(jsonString); import 'dart:convert'; List<Products> productsFromJson(String str) => List<Products>.from(json.decode(str).map((x) => Products.fromJson(x))); String productsToJson(List<Products> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson()))); class Products { int id; String title; double price; String description; Category category; String image; Rating rating; Products({ required this.id, required this.title, required this.price, required this.description, required this.category, required this.image, required this.rating, }); factory Products.fromJson(Map<String, dynamic> json) => Products( id: json["id"], title: json["title"], price: json["price"]?.toDouble(), description: json["description"], category: categoryValues.map[json["category"]]!, image: json["image"], rating: Rating.fromJson(json["rating"]), ); Map<String, dynamic> toJson() => { "id": id, "title": title, "price": price, "description": description, "category": categoryValues.reverse[category], "image": image, "rating": rating.toJson(), }; } enum Category { MEN_S_CLOTHING, JEWELERY, ELECTRONICS, WOMEN_S_CLOTHING } final categoryValues = EnumValues({ "electronics": Category.ELECTRONICS, "jewelery": Category.JEWELERY, "men's clothing": Category.MEN_S_CLOTHING, "women's clothing": Category.WOMEN_S_CLOTHING }); class Rating { double rate; int count; Rating({ required this.rate, required this.count, }); factory Rating.fromJson(Map<String, dynamic> json) => Rating( rate: json["rate"]?.toDouble(), count: json["count"], ); Map<String, dynamic> toJson() => { "rate": rate, "count": count, }; } class EnumValues<T> { Map<String, T> map; late Map<T, String> reverseMap; EnumValues(this.map); Map<T, String> get reverse { reverseMap = map.map((k, v) => MapEntry(v, k)); return reverseMap; } }
Com isso feito, podemos integrá-lo ao nosso aplicativo Flutter.
Volte para sua TestScreenPage
Vamos fazer um pequeno ajuste na função fetch products que criamos anteriormente
Future<void> fetchProducts() async { try { final response = await dio.get('https://fakestoreapi.com/products'); if (response.statusCode == 200) { final List<dynamic> responseData = response.data; setState(() { products = responseData.map((item) => Products.fromJson(item)).toList(); }); } else { print('Request failed with status code: ${response.statusCode}'); } } catch (error) { print('Error: $error'); } }
Vou explicar esse código;
- A função
fetchProducts
é definida como um Future que retorna void. Ela é marcada como assíncrona para permitir o uso deawait
dentro da função. - Dentro da função, um bloco
try
é usado para tratar qualquer erro potencial que possa ocorrer durante a execução. - A palavra-chave
await
é usada para fazer uma solicitação HTTP GET assíncrona usando o pacote Dio. A instância_dio
(que foi inicializada anteriormente) é usada para fazer a solicitação. - O URL “
https://fakestoreapi.com/products
” é o ponto de extremidade do qual estamos obtendo os dados do produto. - A palavra-chave
await
indica que a execução da função será pausada até que uma resposta seja recebida da API. A resposta é atribuída à variável response. - A propriedade
statusCode
do objetoresponse
é verificada para garantir que a solicitação foi bem-sucedida. Um código de status 200 indica uma solicitação bem-sucedida. - Se o código de status for 200, os dados da resposta serão obtidos usando a propriedade
data
deresponse
. Nesse caso, espera-se que os dados da resposta sejam uma lista de objetos dinâmicos que representam os produtos. - A variável
responseData
é declarada como umaList<dynamic>
e recebe o valor dos dados da resposta. - A função
setState
é chamada para atualizar o estado do widget. - A lista de
products
recebe o valor obtido pelo mapeamento de cada item na listaresponseData
para um objetoProducts
usando o método de fábricaProducts.fromJson
. O métodotoList
é usado para converter o iterável mapeado em uma lista. - Se o código de status não for 200, indicando uma solicitação com falha, uma mensagem de erro será impressa no console.
- O bloco
catch
é usado para capturar e tratar quaisquer erros que possam ocorrer durante a execução da função. A variávelerror
contém a mensagem de erro.
Vamos juntar tudo isso
class TestScreen extends StatefulWidget { @override _TestScreenState createState() => _TestScreenState(); } class _TestScreenState extends State<TestScreen> { final Dio _dio = Dio(); List<Products> products = []; @override void initState() { super.initState(); fetchProducts(); } Future<void> fetchProducts() async { try { final response = await _dio.get('https://fakestoreapi.com/products'); if (response.statusCode == 200) { final List<dynamic> responseData = response.data; setState(() { products = responseData.map((item) => Products.fromJson(item)).toList(); }); } else { print('Request failed with status code: ${response.statusCode}'); } } catch (error) { print('Error: $error'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('E-commerce App'), ), body: GridView.builder( padding: EdgeInsets.all(16.0), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, ), itemCount: products.length, itemBuilder: (context, index) { return ProductItem(product: products[index]); }, ), ); } }
Com isso, você está pronto para começar. Você acabou de criar um aplicativo a partir de uma API de comércio eletrônico usando o Dio.
Espero que você tenha se divertido bastante.