Boas práticas do Dart para organizar um projeto
Assim como outras linguagens de programação, o Dart precisa seguir uma série de "normas" ou guias para a boa organização de um projeto.
Esses guias compreendem desde convenções (ou práticas comuns) até peculiaridades que afetam diretamente no funcionamento da linguagem.
Pois bem, é muito importante que você conheça esses guias para desenvolver o seu projeto da melhor forma possível e garantir que ele fique organizado e otimizado.
Pensando nisso, esse artigo vai explorar as melhores dicas de boas práticas no Dart para organizar e otimizar seu projeto. Vamos lá?
Nomenclatura
Vamos começar com a nomenclatura. Basicamente a nomenclatura é a forma que escrevemos nomes de arquivos, nomes de classes, variáveis, entre outras convenções.
Novamente, algumas coisas são apenas convenções e outras afetam o funcionamento do Dart.
Nomeando arquivos
Em primeiro lugar, vamos falar sobre nome de arquivos. Escrevemos nomes de arquivos utilizando o padrão snake_case.
O snake_case é uma forma de escrever onde todos os caracteres são minúsculos e para separar palavras utilizamos um "underscore" ou "underline", e é por conta dessa sinalização que ela se chama snake (cobra).
Veja um exemplo:
minha_classe.dart
Nomeando variáveis
Em variáveis utilizamos o camelCase. O nome camelCase vem da ideia de um camelo (camel) e suas corcovas.
Começamos com a primeira palavra toda minúscula e a cada palavra nova, escrevemos apenas a primeira letra maiúscula.
Veja um exemplo:
String umTextoQualquer = "Aqui temos um texto qualquer";
Nomeando classes
Classes também utilizam o camelCase, mas de uma forma um pouco diferente. Ao invés de começar com a primeira letra minúscula, para diferenciar uma classe de uma variável, começamos com a primeira letra maiúscula e a cada nova palavra também.
Esse tipo é chamado de UpperCamelCase ou PascalCase.
Veja um exemplo:
class ContaCorrente {
double saldo;
ContaCorrente(this.saldo);
}
void main() {
ContaCorrente conta = ContaCorrente(1000);
}
Na definição da classe utilizamos a letra maiúscula no começo:
class ContaCorrente
Para fazer a instância também começamos com a letra maiúscula:
ContaCorrente conta = ...
Mas repare que o nome da variável começa com a letra minúscula.
Palavras-chave e tipos
Todas as palavras-chave do Dart são simples e minúsculas. A única exceção é a palavra Function. Você pode ver a lista completa neste link.
Agora com tipos podemos ter algumas variações. Tipos primitivos no Dart podem ser tanto lowercase quanto UpperCamelCase.
Exemplos de tipo lowercase:
double
int
Exemplos de tipos UpperCamelCase:
String
List
Map
Espaçamentos e indentação
Além de uma boa nomenclatura, a formatação de código também ajuda na legibilidade e organização. Precisamos identificar com facilidade onde um bloco de código começa e termina, quantos parâmetros estão sendo passados, etc.
Vamos ver a seguir algumas formas de espaçamentos comuns no dia a dia.
Indentação
É como chamamos o espaçamento que inicia à esquerda do código. É a partir dele que sabemos onde um bloco de código começa e termina.
Identificamos cada bloco de código pela distância entre ele e a margem do editor/IDE.
Veja o exemplo a seguir:
void main() {
<------ Este espaçamento
String umTexto = "Texto";
bool temEspaco() {
<------ Este espaçamento
return true;
}
}
O primeiro bloco de código não tem espaço. No nosso exemplo, a função main
é iniciada sem nenhum espaço. Mas tudo que pertence ao corpo desta função, precisa de um espaçamento horizontal.
Esse espaçamento pode variar de acordo com o projeto. Não existe uma obrigação, é apenas uma convenção.
Por padrão, esse espaçamento é de dois espaços, ou seja, apertar duas vezes a tecla espaço. Você pode trocar esse padrão na sua IDE ou editor de código.
Dentro da função main
temos outra função temEspaco
. O corpo desta última função precisa também de dois espaços.
Mas atenção, porque ela precisa dos dois espaços da função main
e depois mais dois espaços da função temEspaco
, totalizando quatro espaços.
E isso vai se repetindo enquanto for necessário.
Uma dica para identificar se um código precisa de refatoração é observar quantos espaços estão sendo necessários para identificar blocos de código. Se muitos espaços estão sendo utilizados, talvez a função ou classe precisa ser reescrita. Cuidado, pois podem existir casos em que não há o que fazer, e está tudo bem!
Outros espaçamentos
É complicado categorizar todo tipo de espaçamento, mas existem outras convenções para melhorar a legibilidade de código. Veja o exemplo a seguir:
for (var elemento in lista) {
// fazer coisas
}
Veja os espaços entre as palavras e entre os símbolos. Novamente, é mais uma convenção e depende também do que a sua equipe ou você tem como padrão de escrita. Veja outro exemplo:
ContaCorrente("Matheus", 10000, "333.3333.333-22");
Depois de cada vírgula damos um espaço, como se fosse convenção de escrita de alguma língua como o português.
E falando em visualização, vamos ver agora sobre comentários.
Comentários
Os comentários são formas de indicar e informar detalhes sobre o código que estamos escrevendo, seja como lembrete para outras pessoas desenvolvedoras ou para documentar o que cada parte do código faz e como utilizá-la.
Devemos utilizá-los com mais frequência para manter a organização, a manutenibilidade e a facilidade de uso.
Existem três tipos de comentários em Dart:
- Comentário único;
- Comentário multilinhas;
- Comentário de documentação.
Comentário único
O comentário único é utilizado para pequenas anotações ou avisos. Geralmente, utilizamos para dizer que alguma feature (funcionalidade) precisa ser implementada ou arrumada, comentar o que uma linha de código faz ou, até mesmo, uma forma de dividir partes do código.
Para escrever um comentário único, você pode utilizar duas barras (//) e depois o texto.
Veja alguns exemplos de comentários únicos abaixo:
// Função principal
void main() {
String devolveNome() {
// TODO: fazer a função devolver um nome
return "";
}
var nome = "Matheus"; // Inicializamos um nome "Matheus" à variável nome
}
Comentário multilinhas
O comentário multilinhas tem as mesmas funções de um comentário único, exceto a de colocar comentários ao lado de códigos. Definimos um bloco de comentário utilizando a seguinte sintaxe: /* comentário */
.
Veja alguns exemplos:
/*
Este é um projeto exemplo.
Para iniciar o projeto, é importante ter o Dart instalado em sua máquina.
*/
void main() {
String devolveNome() {
/*
TODO: Implementar a função.
Para fazer a implementação desta função é importante receber um nome dentro da chamada da função. Depois construir uma String única que será devolvida pela função.
*/
return "";
}
}
Comentário de documentação
Como o próprio nome já diz, esse é o tipo de comentário utilizado para documentar o seu código. Extremamente importante se você quer construir e publicar um pacote no pub.dev.
Sabe quando você está utilizando uma IDE como Visual Studio Code e começa a escrever um trecho de código e, de repente, aparece uma sugestão?
Aí você não sabe se a sugestão faz o que você precisa, então você coloca o cursor por cima e lê a descrição dessa sugestão. Esse texto é gerado pelo comentário de documentação!
Para escrever comentários de documentação, utilizamos três barras (///).
Vamos ver um exemplo de uma função:
void main() {
/// Calcula a área de um círculo.
///
/// @param raio O raio do círculo.
/// @return A área do círculo.
double calcularAreaCirculo(double raio) {
return 3.14 * raio * raio;
}
}
O próximo tópico é uma parte muito importante para linguagens que seguem o paradigma de orientação a objetos (POO).
Os princípios de SOLID
É praticamente impossível falar de boas práticas no Dart sem ter um espaço falando sobre SOLID. SOLID (acrônimo) é um conjunto de princípios para linguagens que seguem o paradigma de orientação a objetos.
Eles trazem formas de melhorar a escrita de código, tornando-o mais fácil de manter e escalar.
S de Single Responsibility Principle
É o princípio da responsabilidade única ou SRP. Ele diz que uma classe deve ter apenas um motivo para mudar.
Esse princípio pode não ser imediatamente claro, mas ele indica que uma classe deve ser especializada em uma única tarefa e ter apenas uma responsabilidade dentro do projeto.
Ou seja, evitar de escrever classes com muitas responsabilidades diferentes dentro do projeto.
Um exemplo de responsabilidades:
class Order {
void calculateTotal() {}
void addItem() {}
void removeItem() {}
}
class OrderRepository {
void save() {}
void delete() {}
void update() {}
List<Order> findAll() {}
}
class OrderPrinter {
void printOrder() {}
void showOrder() {}
}
Neste exemplo, cada classe tem uma responsabilidade, respectivamente:
- Representar um pedido e seus valores;
- Conexão com um banco para guardar as informações de pedidos;
- Ferramenta de visualização e pedidos.
Sem SRP tudo ficaria dentro da mesma classe Order
.
O de Open/Closed Principle
É o princípio Aberto-Fechado ou OCP. Objetos ou entidades devem estar abertos para extensão, mas fechados para modificação.
Isso quer dizer que ao invés de editar a classe atual para adicionar uma nova funcionalidade, nós criamos uma nova classe que estende da classe base.
abstract class Produto {
String nome;
double preco;
Produto(this.nome, this.preco);
double calcularFrete();
}
class ProdutoFisico extends Produto {
double peso;
double dimensao;
ProdutoFisico(super.nome, super.preco, this.peso, this.dimensao);
@override
double calcularFrete() {
return 10.0;
}
}
class ProdutoDigital extends Produto {
ProdutoDigital(super.nome, super.preco);
@override
double calcularFrete() {
//Produtos digitais não possuem frete
return 0.0;
}
}
class NotaFiscal {
double total = 0.0;
void calculaTotal(Produto produto) {
total = produto.preco + produto.calcularFrete();
}
}
No código acima, nós temos declarado uma classe de produto que tem as informações básicas. Depois temos dois tipos de produtos (que herdam da classe Produto
) que têm formas diferentes de calcular frete.
Na classe de nota fiscal, independente do tipo de produto, a forma de calcular o valor total é a mesma. Se existir um novo tipo de produto com outra forma de calcular frete, basta apenas criar outra classe e depois sobrescrever o método de cálculo de frete. Desta forma não precisamos alterar nada existente no código.
L de Liskov Substitution Principle
É o princípio da substituição de Liskov ou LSP. Ele diz que uma classe derivada (uma classe que herda de uma classe base) deve ser substituível por sua classe base.
A ideia é que toda classe derivada deve ter o comportamento esperado da classe base.
Vamos pensar em uma classe funcionario
que tem uma função de calcular salário. Depois, criamos uma classe que deriva de funcionario
com a sua própria implementação de cálculo de salário.
O princípio diz que independente de quantas outras classes derivadas de funcionario
existirem, o comportamento de calcular salário deve ser o mesmo, ou seja, ao chamar a função, o salário daquele funcionário deve ser retornado.
Veja a representação em código do que foi falado acima:
abstract class Funcionario {
double calcularSalario();
}
class Gerente extends Funcionario {
@override
double calcularSalario() {
return 2000.0;
}
}
class Vendedor extends Funcionario {
@override
double calcularSalario() {
return 1000.0;
}
}
class Financeiro {
void pagarFuncionario(Funcionario funcionario) {
print('Paga ${funcionario.calcularSalario()}');
}
}
I de Interface Segregation Principle
É o princípio da segregação de interface ou ISP. Ele diz que uma classe não deve ser forçada a implementar interfaces e métodos que não irão utilizar.
Esse princípio é mais tranquilo de entender. Ao invés de ter uma interface única que possui várias funcionalidades, podemos quebrá-la em interfaces menores, assim uma classe pode implementar apenas as funcionalidades necessárias.
Veja um exemplo em código:
abstract class Aves {
void botarOvo();
}
abstract class AvesQueVoam extends Aves {
void voar();
}
class Papagaio implements AvesQueVoam {
@override
void voar() {
print('Papagaio voando');
}
@override
void botarOvo() {
print('Papagaio botando ovo');
}
}
class Pinguim implements Aves {
@override
void botarOvo() {
print("Pinguim botando ovo");
}
}
Este é um exemplo básico do princípio.
D de Dependency Inversion Principle
É o princípio da inversão de dependência ou DIP. Existem algumas formas de descrever esse princípio:
- Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações;
- Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.
Mas e aí? O que queremos dizer com essas descrições? Basicamente queremos dizer que não devemos ter acoplamento.
É quase impossível não existir algum nível de acoplamento em nosso código, mas devemos evitar que ele chegue num nível alto.
Vamos pensar num sistema que faça envio de e-mails para notificação de serviços e produtos. No começo do sistema, utilizamos apenas o sistema do Gmail, mas temos planos para enviar de diversos sistemas diferentes.
Para quem está notificando, não importa se o sistema é Gmail ou Outlook ou qualquer que seja, o que importa é que na hora de notificar por e-mail, ele seja enviado.
Veja o exemplo em código:
abstract class EmailSender {
void enviarEmail(String para, String assunto, String corpo);
}
class GmailSender implements EmailSender {
@override
void enviarEmail(String para, String assunto, String corpo) {
// Lógica para se conectar ao Gmail e enviar o email
}
}
class OutlookSender implements EmailSender {
@override
void enviarEmail(String para, String assunto, String corpo) {
// Lógica para se conectar ao Outlook e enviar o email
}
}
class ServicoDeNotificacao {
// Não importa se será enviado por Gmail ou Outlook, o que importa é que tenhamos a capacidade de enviar email.
final EmailSender _emailSender;
ServicoDeNotificacao(this._emailSender);
void notificarPorEmail(String para, String assunto, String corpo) {
_emailSender.enviarEmail(para, assunto, corpo);
}
}
Para o próximo tópico vamos ver algo que está fortemente atrelado ao SOLID.
Clean Code em Dart
E agora mais uma técnica de escrever códigos limpos e organizados (piada proposital). Clean Code é uma "filosofia de desenvolvimento" de software e, assim como SOLID, tem os seus princípios.
A principal ideia do Clean Code é que códigos bem escritos são mais fáceis de manter, testar e ampliar ao longo do tempo.
Bom, quais são esses princípios?
Nomes significativos
É extremamente importante utilizar nomes para variáveis, funções e classes que realmente refletem o seu funcionamento e propósito.
Para mim (e acho que para muitas outras pessoas desenvolvedoras), a parte mais difícil na hora de escrever código é nomear coisas, mas o Clean Code nos oferece algumas táticas:
- Use nomes de variáveis que tenham significado e sejam pronunciáveis;
- Use um vocabulário consistente para o mesmo tipo de variável;
- Tente escrever nomes que sejam fáceis de pesquisar.
Existem outras técnicas para nomes e se eu fosse colocar todas aqui, o artigo ficaria enorme. No final deste tópico eu vou deixar um link com uma referência para você buscar.
Funções pequenas e com uma única responsabilidade
É comum em algum momento do nosso desenvolvimento criarmos funções que fazem várias coisas. Nossa desculpa costuma ser "mas eu preciso fazer tudo isso para chegar no resultado esperado".
O problema é que talvez o que está sendo executado dentro da função poderia ser quebrado em funções menores, e esse é o princípio.
Quando as funções fazem mais que uma coisa, fica muito difícil de testar e entender seu funcionamento. Isole e simplifique.
Comentários concisos
Documentar o seu código deveria naturalmente fazer parte do processo de desenvolvimento, mas existe uma certa etiqueta para não criar códigos poluídos por dezenas de comentários sem sentido e desnecessários.
É importante comentar/documentar apenas aquilo que foi criado por você. Explique o porquê de ter feito algo. A ideia de ter nomes bem descritivos é evitar a necessidade de comentar variáveis, por exemplo. O código praticamente se autodocumenta;
Evite deixar trechos de código comentados. Hoje em dia todos os códigos usam algum tipo de versionamento com Git, portanto se você quer manter aquele código por algum motivo, crie uma nova branch e não deixe código comentado;
Não use comentários como marcador de posição. Embora a intenção possa ser manter o código bem organizado, na prática isso cria apenas poluição visual. A indentação do código já é suficiente para ajudar na organização.
Formatação consistente
Já foi comentado em outro tópico, mas vale reforçar: o seu código precisa ser escrito de maneira consistente.
Nomes precisam seguir um padrão, arquivos precisam seguir um padrão e espaçamentos precisam seguir um padrão.
Fugir da consistência dificulta a leitura e também a escrita, pois você não tem mais certeza como você deve estender o projeto.
É como misturar inglês, português e alemão em uma mesma pergunta. Como você responde? Em espanhol?
Testes
Um código bem escrito é um código bem testado. Testes garantem o funcionamento da sua aplicação, tornando-a mais robusta e menos propícia a erros.
Se você não possui testes ou uma quantidade insuficiente deles, você não tem a certeza de que tudo está funcionando de acordo.
A aplicação pode até não quebrar, mas você não sabe se ela não quebrou porque o código está funcionando corretamente ou porque faltou pegar alguma exception
no meio do caminho.
Um processo de desenvolvimento que anda em conjunto com Clean Code é TDD (Test Driven Development) ou Desenvolvimento Orientado a Testes.
Inclusive, se você quiser conhecer mais sobre TDD, indico esse material incrível sobre TDD em Flutter: desenvolvimento orientado a testes.
SOLID
E olha só! SOLID faz parte de um dos princípios de Clean Code. E não é muita surpresa já que ambos foram escritos pela mesma pessoa (Robert C. Martin. Também conhecido como Uncle Bob). Seguir os princípios de SOLID ajudam a desenvolver um código limpo e organizado.
Como prometido, se você quiser ver mais sobre Clean Code no Dart, existe um guia criado por William Barreiro.
Agora vamos partir para o último tópico deste artigo: organização de pastas.
Organizando pastas
Em um projeto Dart, temos duas pastas principais: bin
e lib
. Quando iniciamos um projeto Dart novo, temos um arquivo com o nome do projeto nessas duas pastas, mas o arquivo com a função main
fica apenas na pasta bin
.
Então, vamos entender como funciona essa organização. Dentro da pasta bin
deve ficar apenas o necessário para que o seu programa seja executado.
Isso quer dizer apenas um arquivo com o nome do projeto e uma função main
. Normalmente a função main
contém poucas instruções.
Agora tudo que compõe a aplicação precisa ficar na pasta lib
. Todas as classes, interfaces, mixins, banco de dados, conexões com APIs, views, controllers e models ficam todas dentro da pasta lib
.
Veja um exemplo de organização de pastas:
my_app/
├── bin/
│ └── my_app.dart
├── lib/
│ ├── models/
│ │ ├── user.dart
│ │ └── product.dart
│ ├── views/
│ │ ├── home_page.dart
│ │ └── product_detail_page.dart
│ ├── controllers/
│ │ ├── user_controller.dart
│ │ └── product_controller.dart
│ └── utils/
│ ├── helpers.dart
│ └── constants.dart
├── test/
│ ├── models_test.dart
│ └── views_test.dart
└── pubspec.yaml
A pasta test
é onde você coloca todos os testes da sua aplicação. Importante separar cada parte que será testada em um arquivo diferente. Este é um caso obrigatório, pois o Dart busca testes nesta pasta.
Existe uma pasta extra que não vem por padrão mas é utilizada como convenção: a pasta assets
. Essa pasta é onde você coloca imagens, vídeos, fontes e outros arquivos textuais.
Os arquivos na pasta assets
não são compilados junto com o código Dart, eles são incluídos como recursos estáticos no aplicativo.
Outro ponto importante é que a pasta assets
deve ficar na raiz do seu projeto, junto com as pastas bin
, lib
e test
.
A pasta assets
é comumente usada apenas no Flutter, principalmente para exibição de imagens, vídeos e tipografias dentro do aplicativo. Essa pasta é apontada através de um parâmetro no arquivo pubspec.yaml
.
Em um projeto Dart, a pasta assets
não é tão utilizada. Utilizamos o pacote dart:io
para leitura e escrita de arquivos e a sua localização pode variar de acordo com a necessidade. Não existe um padrão de nomenclatura ou estrutura.
O sistema de escrita e leitura de arquivos pode ser um pouco complexo, mas você pode ler mais sobre esse assunto na documentação oficial.
A pasta
assets
não funciona como um banco de dados.
Dependências do projeto
Agora mantendo a ideia de organização mas saindo do conceito de pastas, temos o arquivo na raiz do projeto Dart chamado pubspec.yaml
.
Neste arquivo, colocamos todas as informações relacionadas às dependências do projeto, ou seja, quais pacotes precisam ser baixados e instalados do pub.dev para fazer o projeto funcionar, quais os assets que serão utilizados, qual versão de sdk, entre outras configurações do projeto.
Em novos projetos Dart, esse arquivo é gerado com menos informações. Veja um exemplo:
name: nome_do_projeto
description: A sample command-line application.
version: 1.0.0
# repository: https://github.com/my_org/my_repo
environment:
sdk: ^3.5.1
# Add regular dependencies here.
dependencies:
# path: ^1.8.0
dev_dependencies:
lints: ^4.0.0
test: ^1.24.0
Em novos projetos Flutter, esse arquivo é gerado com mais informações. Veja um exemplo:
name: nome_do_projeto
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: ^3.5.1
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
É muito interessante comparar um arquivo com o outro e ver a quantidade de instruções e informações extras que o pubspec.yaml
do Flutter tem, né?
Conclusão
Concluir um projeto Dart bem organizado requer atenção aos detalhes e seguir boas práticas, desde a nomenclatura adequada para arquivos, variáveis e classes, até a organização de pastas e a utilização correta de espaçamentos e comentários.
Ou seja, tudo contribui para um código mais legível, manutenível e eficiente.
Agora você já tem tudo que é necessário para começar projetos Dart e Flutter da melhor forma possível!
Lembre-se que é importante seguir boas práticas de código estabelecidas pelo mercado, isso facilita a entrada de novas pessoas no projeto.
Além disso, é bacana ter em mente que as informações presentes neste artigo podem servir como base para você encontrar a melhor solução para o seu projeto!
Agora, fica o meu convite para que você aprofunde seus conhecimentos em Dart e Flutter! Vamos lá?
Caso queira mais conteúdos sobre este tema, acesse o link:
Até a próxima!