Alura > Cursos de Mobile > Cursos de Flutter > Conteúdos de Flutter > Primeiras aulas do curso Dart: lidando com erros, exceções e null safety

Dart: lidando com erros, exceções e null safety

Realizando primeira exceção - Apresentação

Olá, meu nome é Ricarth Lima, e serei seu instrutor em mais um curso de Dart!

Audiodescrição: Ricarth é um homem com cabelo black power, utiliza óculos de armação quadrada transparente, possui pele dourada, barba e bigode. Está de camiseta preta, e o fundo, de parede branca, está iluminada por luzes em tons de roxo e azul, e há prateleiras com livros diversos e alguns bonecos.

Boas-vindas ao curso de Dart: lidando com erros, exceções e null safety. Este assunto é muito importante, estou muito empolgado. O projeto do curso será uma continuação direta do curso anterior, um chatbot do Banco d'Ouro, porém, com um detalhe: no curso anterior não preparamos o programa para eventuais erros, uma situação excepcional como uma queda de internet ou a não resposta do servidor, entre outras.

Não preparamos uma mensagem amigável que traga a clareza desejada, sem o qual nosso programa quebraria:

Não foi possível alcançar o servidor.
Tente novamente mais tarde.
Failed host lookup: 'api.github.com.br'
https://api.github.com.br/gists/413c0aefe6c6abc464581c29029c8ace
2024-08-07 16:24:14.004242 | ocorreu uma tentativa de consulta.
Como eu posso te ajudar? (digite o número desejado)
1 - Ver todas suas contas.
2 - Adicionar nova conta.
3 - Sair

Esse pensamento de que nem sempre o caminho feliz vai ocorrer, e que precisamos preparar o programa para que ele lide com caminhos excepcionais é muito importante. É essencial termos essa noção para entrada no mercado de trabalho usando Dart e Flutter, pois a aplicação não pode quebrar, travar, ficar carregando infinitamente na tela da pessoa usuária.

Atenção! Como pré-requisito, é importante ter realizado o curso anterior de Dart com aassincronismo e APIs, justamente porque trabalharemos no mesmo projeto, e os conhecimentos aprendidos neste curso serão úteis.

Estou muito animado para começar, vamos por a mão na massa?

Realizando primeira exceção - Lidando com problemas

Estamos desenvolvendo uma aplicação que gerencia um chatbot para as contas do nosso Banco d'Ouro, que começamos no curso anterior. No entanto, há um grande detalhe a ser considerado. Vamos abrir o terminal com "Ctrl + J", ajustar sua posição para melhorar a visualização e executar usando dart run bin/main.dart. Vamos ver como isso se comporta.

Perfeito! O chatbot, Lewis, aparece corretamente. Se pressionarmos "Enter", ele exibe o resultado. O número 2 adiciona uma nova opção, e com o número 3, saímos. Você notou algum problema aqui? Exatamente, nenhum! Essa resposta pode parecer estranha, mas é porque estamos apenas considerando o cenário ideal do nosso projeto.

Quando começamos a programar, temos a tendência, como pessoas desenvolvedoras, de pensar apenas nesse cenário ideal. Ou seja, se nada der errado, qual será o caminho que a pessoa usuária seguirá? No entanto, esse "se nada der errado" é muito otimista. Na vida real, quando lidamos com aplicações conectadas à Web ou com aplicações em dispositivos com baixa capacidade de hardware, como os dispositivos móveis em comparação aos computadores, por exemplo, erros podem surgir e fugir do nosso controle. Nosso programa precisa estar preparado para lidar com essas situações.

Nessa aplicação, que ainda é bastante simples, o que poderia dar errado de imediato? Talvez a internet da pessoa usuária caia. Será que o programa vai travar? Não deveria. E se tivéssemos passado um servidor incorreto? Se, ao tentarmos nos comunicar com nosso servidor, nossa API, por algum motivo ou ação da pessoa usuária, acabamos indo parar num servidor incorreto. O que acontecerá?

Para não ter que desconectar o cabo de internet e provar que algo acontece quando a internet cai, vamos alterar intencionalmente nosso servidor. Então, se formos até a estrutura do projeto usando o atalho "Ctrl + B", teremos a pasta services. Dentro da pasta de serviços, temos account_service.dart. Na linha 12 deste arquivo, veremos que chamamos a API do github.com no subdiretório gists com o ID do nosso gist. E se em vez de ".com", digitássemos ".br" por engano? Vamos salvar e rodar para ver o que acontece.

Após salvarmos, vamos abrir o terminal com "Ctrl + J", limpá-lo usando o comando clear e rodar dart run bin/main.dart. O chatbot rodou, vamos tentar visualizar uma conta digitando a opção "1" no terminal, o que fará a primeira requisição com o servidor. Aconteceu um erro inesperado.

Enfatizaremos muito neste curso que os erros, as exceções, os problemas que o Dart nos dá não são inimigos. Pelo contrário. Sabemos que quando começamos a programar, é comum sentir um pouco de desespero nestes momentos. O coração bate mais rápido, você começa a pensar que fez algo errado, não sabe o que fazer, até porque muitas vezes esses erros vêm em inglês, e por aí vai.

Precisamos inicialmente nos acalmar e ler detalhadamente o que nos foi dito. Na primeira linha, vemos que uma exceção não foi tratada. Ou seja, chegamos a um caso excepcional, em um caminho que não é o ideal e não lidamos com isso, o que leva o programa a quebrar.

A seguir, temos que houve uma ClientException com SocketException. São dois problemas que existem dentro do pacote HTTP, além de informações adicionais. Falhamos em nos comunicar com esse host, api.github.com.br. O host não é conhecido, já que nós forçamos essa situação, mas isso poderia ter acontecido de outra maneira. Poderíamos checar no código e verificar que de fato colocamos .br em vez de .com.br.

Seguindo na leitura das mensagens de erro, as linhas que mostram #0, #1, #2, indicam o caminho que a exceção não tratada tomou. Então, o primeiro método que causou o erro não estava sob nosso controle, porque ele estava no pacote HTTP, o método send do IOClient. Depois, vemos o BaseClient._sendUnstreamed, que também não controlamos, assim como o _withClient. Se prestarmos atenção em cada uma dessas linhas, tanto a #0, quanto a #1 e a #2, são do pacote (package) HTTP.

Na linha 3, o AccountService.getAll indica uma classe chamada AccountService que tem um método getAll. O pacote é indicado: o dart_exceptions, o services, na pasta serviço, com o account_service.dart. Além disso, são indicados a linha e a posição do caractere onde o erro se deu: linha 16 na posição 25. Para confirmar, podemos usar o "Ctrl + J" para encontrar a linha 16, e imaginar que muito provavelmente o erro se dá no get.

Voltando às mensagens de erro no terminal, também temos que o getAll não soube lidar com isso, "enviando" o problema para o _getAllAccounts, que fica na nossa tela, a qual também não soube lidar, mandando a exceção para "mais longe" ainda, o runChatBot que fica na main, que por fim não soube lidar, e isso matou a aplicação, pois não foi mais possível rodá-la.

Nota-se que não é para ter medo deste tipo de situação. É importante reforçar que todos os erros que surgirem, principalmente em pacotes bem escritos como o HTTP, vão nos dando pistas para conseguirmos corrigi-los da melhor forma possível.

Diferentemente deste exemplo, em que forçamos uma exceção, a seguir aprenderemos a lidar com elas de fato!

Realizando primeira exceção - Usando o try

Entendemos que erros podem ocorrer e que o que aparece na tela não é nosso inimigo, pelo contrário, nos ajudam a entender o que está acontecendo no código. No entanto, a pessoa usuária da nossa aplicação não tem acesso a todo esse código em inglês, que tampouco vai significar algo para ela, certo? Para ajudá-la, precisamos tratar essa exceção.

Nosso terminal indica que houve uma exceção que não foi tratada, e ela representa justamente um caminho que não é ideal, ou seja, aquele que projetamos para ser perfeito. Quando falamos de exceção no Dart, estamos nos referindo a um objeto. Tudo é um objeto, portanto a exceção não é diferente. Trata-se de um objeto a ser lançado por quem o notou. Neste caso, quando o problema ocorre, o HTTP lança o que chamamos de exceção. Mas, se não o capturarmos em algum lugar, o problema vai sendo "escalado" para trechos de código mais externos, que vão repassando-o, até a aplicação travar.

Para evitar isso, vamos identificar onde o getAll da linha 3 está sendo chamado. Temos que é na AccountScreen, no _getAllAccounts. Vamos para lá usando "Ctrl + J" e "Ctrl + B" para abrir o explorador. Na pasta "screens", abriremos account_screen.dart, e usaremos o "Ctrl + B" de novo, para fecharmos o explorador. Na parte de baixo do código, há o _getAllAccounts(), e na linha de baixo, a 59, o getAll() está sendo chamado, e ela pode lançar uma exceção.

Para tratarmos a exceção, cercaremos esta linha e tudo que depende dela com o famoso bloco Try, que significa "tentar" em inglês. Ou seja, estamos indicando ao Dart: "Tente executar esse bloco de código com pelo menos uma linha que pode causar uma exceção". Na linha 58, vamos quebrar uma linha, escrever Try, e tirar a chave do final, colocando-a na linha 62:

_getAllAcounts() async {
    try{
    List<Account> listAccounts = await _accountService.getAll();
    print(listAccounts);
    }
}

Porém, o bloco try não é o suficiente, pois se alguma exceção acontecer, o Dart precisa saber o que fazer. Um bloco try precisa ser seguido por um on, um catch ou um finally: um on pode significar que uma exceção foi identificada, um catch significa pegar essa exceção identificada, e um finally basicamente informa ao programa que o bloco deverá ser rodado, não importa se uma exceção foi identificada ou não. Por enquanto, vamos começar com um on Exception:

_getAllAcounts() async {
    try{
    List<Account> listAccounts = await _accountService.getAll();
    print(listAccounts);
    } on Exception {
    
    }
}

Quando a aplicação notar uma exceção, ela vai tentar rodar o bloco das linhas 61 a 62, dentro do try. Se houver de fato uma exceção, o bloco que começa na linha 63 será rodado. Este bloco terá algo super plausível de se ter num chatbot, por exemplo a mensagem "não consegui recuperar os dados da conta" em um print, seguido de outro:

_getAllAcounts() async {
    try{
    List<Account> listAccounts = await _accountService.getAll();
    print(listAccounts);
    } on Exception {
    print("Não consegui recuperar os dados da conta.");
    print("Tente novamente mais tarde.");
    }
}

Deste modo, a nova linha 60 pode ocasionar um problema. Escolhemos lidar com possíveis exceções no getAll(). Cercamos a linha 60 e a dependente, 61, com o bloco try. Como o próprio Dart diz, o try não funciona sozinho, e no nosso caso, usamos o on para indicar o que deve ser feito: comparar se essa exceção é uma Exception. Vamos rodar e ver o que acontece com o "Ctrl + J" e dart run bin/main.dart.

Inicialmente, parece que funcionou. O problema é quando tentamos consultar todas as contas. Digitaremos a opção "1", e temos um resultado muito melhor! Tivemos duas mudanças muito importantes: primeiro, recebemos um feedback. Notamos uma situação problemática e conseguimos tratá-la.

Segunda mudança, e a mais importante: a aplicação não travou, justamente porque a exceção não lidada não chegou na nossa main. Ótimo, agora vamos aprofundar mais nas possibilidades do try.

Sobre o curso Dart: lidando com erros, exceções e null safety

O curso Dart: lidando com erros, exceções e null safety possui 150 minutos de vídeos, em um total de 60 atividades. Gostou? Conheça nossos outros cursos de Flutter em Mobile, ou leia nossos artigos de Mobile.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Flutter acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas