Uma parte crucial do desenvolvimento de aplicativos é lidar com as solicitações de rede de maneira adequada. As respostas retornadas por uma rede podem consistir em resultados inesperados e, para ter uma boa experiência do usuário, você precisa cuidar dos casos extremos com antecedência.

Neste artigo, daremos uma olhada em como lidar com as solicitações da API REST no Flutter usando o pacote Dio.

O que é Dio?

Dio é um cliente HTTP poderoso para Dart . Possui suporte para interceptores, configuração global, FormData , cancelamento de solicitação, download de arquivo e timeout, entre outros. O Flutter oferece um pacote http que é bom para realizar tarefas básicas de rede, mas é muito difícil de usar ao lidar com alguns recursos avançados. Em comparação, o Dio fornece uma API intuitiva para realizar tarefas de rede avançadas com facilidade.

Primeiros passos

Vamos começar criando um novo projeto Flutter. Use o seguinte comando:

 vibração criar dio_networking

Você pode abrir o projeto usando seu IDE favorito, mas, para este exemplo, serei usando o código VS :

 código dio_networking

Adicione o pacote Dio ao seu arquivo pubspec.yaml :

dependências

: dio: ^ 4.0.0

Substitua o conteúdo do seu arquivo main.dart pelo seguinte:

 import'package: flutter/material.dart'; void main () { runApp (MyApp ());
} class MyApp extends StatelessWidget { @sobrepor Construção de widget (contexto BuildContext) { return MaterialApp ( título:'Dio Networking', tema: ThemeData ( primarySwatch: Colors.blue, ), debugShowCheckedModeBanner: false, home: HomePage (), ); }
}

Definiremos a classe HomePage após buscar os dados da rede.

Agora, vamos dar uma olhada nos dados de rede que usaremos para a demonstração.

Teste com dados API

Usaremos REQ | RES para testar nossos dados de rede porque fornece uma API REST hospedada que consiste em dados de usuário de amostra e permite que você execute uma variedade de testes de operação de rede.

REQ | site RES

Começaremos fazendo uma solicitação GET simples para buscar dados de Usuário Único . O endpoint necessário para isso é:

 GET https://reqres.in/api/users/

Observe aqui que deve ser substituído por um valor inteiro que corresponda e seja usado para localizar um usuário específico.

Abaixo está a aparência de um exemplo de resposta JSON quando a solicitação é bem-sucedida:

 { "dados": { "id": 2, "email":"[email protected]", "first_name":"Janet", "last_name":"Weaver", "avatar":"https://reqres.in/img/faces/2-image.jpg" }
}

Definindo uma classe de modelo

Se você deseja manipular facilmente os dados retornados de uma solicitação REST API, deve definir uma classe de modelo.

Por enquanto, definiremos apenas uma classe simples para armazenar dados de um único usuário. Você pode usar o código Dart puro ou uma biblioteca de forma intercambiável, sem fazer nenhuma outra alteração no mesmo aplicativo de exemplo. Definiremos uma classe de modelo manualmente como esta:

 class User { Do utilizador({ exigido this.data, }); Dados de dados; fábrica User.fromJson (Map  json)=> Usuário ( data: Data.fromJson (json ["data"]), ); Mapear  toJson ()=> { "data": data.toJson (), };
} class Data { Dados({ exigido this.id, necessário este e-mail, necessário this.firstName, necessário this.lastName, necessário this.avatar, }); int id; String email; String firstName; String lastName; Avatar de string; fábrica Data.fromJson (Map  json)=> Dados ( id: json ["id"], email: json ["email"], firstName: json ["first_name"], lastName: json ["last_name"], avatar: json ["avatar"], ); Mapear  toJson ()=> { "Eu fiz, "email": email, "Primeiro NomePrimeiro Nome, "last_name": lastName, "avatar": avatar, };
}

Para evitar erros despercebidos que podem ocorrer durante a definição manualmente, você pode usar a serialização JSON e gerar os métodos de fábrica automaticamente.

Para isso, você precisará dos seguintes pacotes:

Adicione-os ao seu arquivo pubspec.yaml :

dependências

: json_annotation: ^ 4.0.1 dev_dependencies: json_serializable: ^ 4.1.3 build_runner: ^ 2.0.4

Separe as classes de usuário e de dados em dois arquivos Dart- user.dart e data.dart , respectivamente-e modifique seu conteúdo.

O conteúdo da classe User será o seguinte:

 import'package: json_annotation/json_annotation.dart'; import'data.dart'; parte'user.g.dart'; @JsonSerializable ()
class User { Do utilizador({ exigido this.data, }); Dados de dados; fábrica User.fromJson (Map  json)=> _ $ UserFromJson (json); Map  toJson ()=> _ $ UserToJson (this);
}

O conteúdo da classe Dados será o seguinte:

 import'package: json_annotation/json_annotation.dart'; parte'data.g.dart'; @JsonSerializable ()
class Data { Dados({ exigido this.id, exigiu este e-mail, necessário this.firstName, necessário this.lastName, necessário this.avatar, }); int id; String email; @JsonKey (nome:'first_name') String firstName; @JsonKey (nome:'last_name') String lastName; Avatar de string; fábrica Data.fromJson (Map  json)=> _ $ DataFromJson (json); Map  toJson ()=> _ $ DataToJson (this);
}

Os métodos fromJson e toJson serão gerados pelo pacote json_serializable . Alguns dos atributos de classe são anotados com @JsonKey porque o nome definido no Mapa (e retornado pela solicitação de API) é diferente de seu nome de atributo.

Você pode acionar a geração do código usando o seguinte comando:

compilação build_runner do

 flutter pub run

Mantenha o gerador de código em execução em um servidor para que qualquer nova alteração na classe acione automaticamente a geração do código. Use o seguinte comando para fazer isso:

 flutter pub run build_runner serve--delete-conflito-saídas

O sinalizador --delete-conflito-outputs ajuda a regenerar uma parte da classe gerada se algum conflito for encontrado.

Inicializar Dio

Você pode criar uma classe separada contendo os métodos para realizar as operações de rede. Isso ajuda a separar a lógica funcional do código da interface do usuário.

Para fazer isso, crie um novo arquivo dio_client.dart contendo a classe DioClient :

 class DioClient { //TODO: Configure e defina os métodos para operações de rede
}

Você pode inicializar o Dio usando o seguinte:

 import'package: dio/dio.dart'; class DioClient { final Dio _dio=Dio ();
}

Defina o URL base do servidor API:

 import'package: dio/dio.dart'; class DioClient { final Dio _dio=Dio (); final _baseUrl='https://reqres.in/api'; //TODO: Adicionar métodos
}

Agora, podemos definir os métodos necessários para realizar as solicitações de rede.

Definindo a solicitação GET

Definiremos um método para recuperar os dados de um único usuário da API, passando um id :

 Future  getUser ({string id obrigatória}) async { //Execute a solicitação GET para o endpoint"/users/" Resposta userData=await _dio.get (_baseUrl +'/users/$ id'); //Imprime os dados brutos retornados pelo servidor print ('Informações do usuário: $ {userData.data}'); //Analisando os dados JSON brutos para a classe de usuário User user=User.fromJson (userData.data); usuário de retorno;
}

O método acima funciona, mas se houver algum erro de codificação aqui, o aplicativo irá travar quando você executá-lo.

Uma maneira melhor e mais funcional de fazer isso é envolver o método get () com um bloco try-catch :

 Future  getUser ({string id obrigatória}) async { Do utilizador? do utilizador; tentar { Resposta userData=await _dio.get (_baseUrl +'/users/$ id'); print ('Informações do usuário: $ {userData.data}'); user=User.fromJson (userData.data); } no DioError catch (e) { //A solicitação foi feita e o servidor respondeu com um código de status //isso está fora do intervalo de 2xx e também não é 304. if (e.response!=null) { imprimir ('Erro Dio!'); print ('STATUS: $ {e.response?.statusCode}'); print ('DATA: $ {e.response?.data}'); imprimir ('CABEÇALHOS: $ {e.response?.headers}'); } senão { //Erro devido à configuração ou envio da solicitação print ('Erro ao enviar solicitação!'); imprimir (mensagem eletrônica); } } usuário de retorno;
}

Neste exemplo, também tornamos o User anulável para que, em caso de qualquer erro, o servidor retornará null em vez de quaisquer dados reais do usuário.

Para exibir os dados do usuário, temos que construir a classe HomePage . Crie um novo arquivo chamado home_page.dart e adicione o seguinte a ele:

 class HomePage extends StatefulWidget { @sobrepor _HomePageState createState ()=> _HomePageState ();
} class _HomePageState extends State  { DioClient final _client=DioClient (); @sobrepor Construção de widget (contexto BuildContext) { return Scaffold ( appBar: AppBar ( título: Texto ('Informações do usuário'), ), corpo: Centro ( filho: FutureBuilder  ( futuro: _client.getUser (id:'1'), construtor: (contexto, instantâneo) { if (snapshot.hasData) { Do utilizador? userInfo=snapshot.data; if (userInfo!=null) { Dados userData=userInfo.data; return Column ( mainAxisSize: MainAxisSize.min, crianças: [ Image.network (userData.avatar), SizedBox (altura: 8,0), Texto( '$ {userInfo.data.firstName} $ {userInfo.data.lastName}', style: TextStyle (fontSize: 16.0), ), Texto( userData.email, style: TextStyle (fontSize: 16.0), ), ], ); } } retornar CircularProgressIndicator (); }, ), ), ); }
}

Dentro da classe _HomePageState , o DioClient é instanciado primeiro. Então, dentro do método build , um FutureBuilder é usado para recuperar e mostrar os dados do usuário. Um CircularProgressIndicator será exibido enquanto o resultado está sendo obtido.

O aplicativo de amostra

Definindo a solicitação POST

Você pode usar uma solicitação POST para enviar dados à API. Vamos tentar enviar uma solicitação e criar um novo usuário.

Primeiro, definirei outra classe de modelo, porque as propriedades desses dados JSON serão diferentes da classe de modelo User definida anteriormente, para lidar com as informações do usuário que temos que enviar:

 import'package: json_annotation/json_annotation.dart'; parte'user_info.g.dart'; @JsonSerializable ()
class UserInfo { Nome da string; String job; Fragmento? eu ia; Fragmento? criado em; Fragmento? updatedAt; Informação de usuário({ necessário this.name, exigido this.job, this.id, this.createdAt, this.updatedAt, }); fábrica UserInfo.fromJson (Map  json)=> _ $ UserInfoFromJson (json); Map  toJson ()=> _ $ UserInfoToJson (this);
}

Especifique um método dentro da classe DioClient para criar um novo usuário:

 Future  createUser ({required UserInfo userInfo}) async { Informação de usuário? retrieveUser; tentar { Resposta de resposta=await _dio.post ( _baseUrl +'/users', dados: userInfo.toJson (), ); print ('Usuário criado: $ {response.data}'); retrieveUser=UserInfo.fromJson (response.data); } catch (e) { print ('Erro ao criar usuário: $ e'); } return retrievedUser;
}

Isso leva um objeto UserInfo como parâmetro, que então envia para o endpoint /users da API. Ele retorna uma resposta com as informações do usuário recém-criadas e a data e hora de criação.

Informações do usuário recém-criadas painel

Definindo a solicitação PUT

Você pode atualizar os dados presentes no servidor API usando uma solicitação PUT.

Para definir um novo método para atualizar um usuário dentro da classe DioClient , temos que passar o objeto UserInfo atualizado junto com o id do usuário a quem queremos aplicar a atualização.

 Futuro  updateUser ({ UserInfo userInfo obrigatório, id de string obrigatório,
}) assíncrono { Informação de usuário? updatedUser; tentar { Resposta de resposta=await _dio.put ( _baseUrl +'/users/$ id', dados: userInfo.toJson (), ); print ('Usuário atualizado: $ {response.data}'); updatedUser=UserInfo.fromJson (response.data); } catch (e) { print ('Erro ao atualizar usuário: $ e'); } return updatedUser;
}

O código acima enviará uma solicitação PUT ao endpoint /users/ junto com os dados UserInfo . Em seguida, ele retorna as informações do usuário atualizadas e a data e hora da atualização.

Informações do usuário atualizadas

Definindo a solicitação DELETE

Você pode excluir alguns dados do servidor usando uma solicitação DELETE.

Defina um novo método dentro da classe DioClient para excluir um usuário do servidor API passando o id do usuário.

 Futuro  deleteUser ({string id obrigatória}) async { tentar { esperar _dio.delete (_baseUrl +'/users/$ id'); imprimir ('Usuário excluído!'); } catch (e) { print ('Erro ao excluir usuário: $ e'); }
}

Informações do usuário excluídas

Escolha e definição de sua base

Em vez de passar o endpoint com baseUrl todas as vezes, você pode apenas defini-lo dentro de BaseOptions e passá-lo uma vez enquanto instancia Dio ./p>

Para fazer isso, você deseja inicializar o Dio da seguinte maneira:

 final Dio _dio=Dio ( BaseOptions ( baseUrl:'https://reqres.in/api', connectTimeout: 5000, receiveTimeout: 3000, ),
);

Este método também fornece várias outras personalizações-neste mesmo exemplo, definimos connectTimeout e receiveTimeout para as solicitações.

Carregando arquivos

Dio torna o processo de envio de arquivos para um servidor muito mais simples. Ele pode processar vários uploads de arquivos simultâneos e tem um retorno de chamada simples para rastrear seu progresso, o que o torna muito mais fácil de usar do que o pacote http .

Você pode facilmente fazer upload de arquivos para um servidor usando FormData e Dio. Aqui está um exemplo de como seria o envio de um arquivo de imagem para a API:

 String imagePath; FormData formData=FormData.fromMap ({ "image": await MultipartFile.fromFile ( imagePath, nome do arquivo:"upload.jpeg", ),
}); Resposta de resposta=await _dio.post ( '/procurar', data: formData, onSendProgress: (int enviado, int total) { print ('$ enviado $ total'); },
);

Interceptadores

Você pode interceptar solicitações, respostas e erros do Dio antes de serem manipulados usando then ou catchError . Em um cenário prático, os interceptores são úteis para autorização usando JSON Web Tokens (JWT) , analisando JSON, tratamento de erros e depuração fácil de solicitações de rede Dio.

Você pode executar o interceptor substituindo os retornos de chamada em três lugares: onRequest , onResponse e onError .

Para nosso exemplo, definiremos um interceptor simples para registrar diferentes tipos de solicitações. Crie uma nova classe chamada Logging que se estende de Interceptor :

 import'package: dio/dio.dart'; class Logging extends Interceptor { @sobrepor void onRequest (opções RequestOptions, manipulador RequestInterceptorHandler) { print ('PEDIDO [$ {options.method}]=> CAMINHO: $ {options.path}'); return super.onRequest (opções, manipulador); } @sobrepor void onResponse (resposta de resposta, manipulador ResponseInterceptorHandler) { impressão( 'RESPOSTA [$ {response.statusCode}]=> CAMINHO: $ {response.requestOptions.path}', ); retornar super.onResponse (resposta, manipulador); } @sobrepor void onError (DioError err, manipulador ErrorInterceptorHandler) { impressão( 'ERROR [$ {err.response?.StatusCode}]=> CAMINHO: $ {err.requestOptions.path}', ); return super.onError (err, manipulador); }
}

Aqui, substituímos vários retornos de chamada que são acionados por solicitações Dio e adicionamos uma instrução de impressão a cada um deles para registrar as solicitações no console.

Adicione o interceptor ao Dio durante a inicialização:

 final Dio _dio=Dio ( BaseOptions ( baseUrl:'https://reqres.in/api', connectTimeout: 5000, receiveTimeout: 3000, ), ).. interceptors.add (Logging ());

Os resultados registrados no console de depuração serão parecidos com:

Resultados registrados no debug console

Conclusão

Networking no Flutter usando Dio parece uma brisa e graciosamente lida com muitos casos extremos. O Dio torna mais fácil lidar com várias solicitações de rede simultâneas, tudo com a segurança de uma técnica avançada de tratamento de erros. Ele também permite que você evite o código clichê de que você precisa para usar o pacote http para rastrear qualquer progresso de upload de arquivo. E há várias outras personalizações avançadas que você pode realizar usando o pacote Dio que vai além do que cobrimos aqui.

Obrigado por ler o artigo! Se você tiver sugestões ou perguntas sobre o artigo ou exemplos, sinta-se à vontade para entrar em contato comigo no Twitter ou LinkedIn . Você também pode encontrar o repositório do aplicativo de amostra em meu GitHub .

A postagem Networking in Flutter using Dio apareceu primeiro em LogRocket Blog .