• Home

    Escolha seu Plano

    Carreiras Alura

    Evolua com profundidade técnica, direção clara e aplicação prática.

    Jornadas guiadas do básico ao avançado com checkpoints práticos, desenhadas para quem busca profundidade e protagonismo técnico.

    Iniciante

    Comece do zero com uma trilha clara e progressiva.

    Fundamentos práticos para dar os primeiros passos com segurança e construir uma base sólida.

    Intermediário

    Continue sua evolução com cursos que conectam fundamentos, ferramentas e prática profissional. Ganhe autonomia, e avance para projetos mais completos.

    Avançado

    Aprofunde temas técnicos e estratégicos para enfrentar desafios mais complexos. Explore boas práticas, arquitetura, performance com visão profissional.

    Cursos

    Ver mais cursos de Back-end
    • Pensamento computacional: fundamentos da computação e lógica de programação
    • Lógica de programação: mergulhe em programação com JavaScript
    • DDD: fundamentos do design orientado a domínio
    • ASP.NET: autenticação e autorização em APIs e aplicações web
    • Microsserviços: migração de monólitos e modularização

    Cursos

    Ver mais cursos de Dados
    • Python para Dados: primeiros passos
    • Excel: domine o editor de planilhas
    • Engenharia de Dados: Orquestração de Pipelines com Apache Airflow
    • Power BI Desktop: construindo meu primeiro dashboard
    • Governança e Arquitetura de Dados

    Cursos

    Ver mais cursos de Inteligência Artificial
    • IA: Explorando o Potencial da Inteligência Artificial Generativa
    • Engenharia de Prompt: Criando Prompts Eficazes para IA Generativa
    • Claude para Análise de Dados: prompts, integrações e automações
    • Agno: criando agentes e sistemas multiagente
    • Engenharia de software na era da IA: segurança de aplicações com agentes, MCPs e código gerado por IA

    Cursos

    Ver mais cursos de Front-end
    • HTML e CSS: ambiente, estrutura e estilo
    • React: realizando testes avançados com Jest e Testing Library
    • React: integrando TypeScript em projetos
    • JavaScript: aprendendo a programar
    • React: aplicando arquiteturas de Micro frontends

    Cursos

    Ver mais cursos de DevOps
    • Sistema Operacional Linux: fundamentos e administração prática
    • Containers e Docker: empacotamento, isolamento e gestão
    • Redes: dos conceitos iniciais à criação de uma intranet
    • Linux: gerenciando diretórios, arquivos, permissões e processos
    • Cibersegurança: Fundamentos e práticas integradas

    Cursos

    Ver mais cursos de UX & Design
    • UX Research: mapeando a experiência da pessoa usuária
    • UX/UI Design: Entregando produtos digitais com IA
    • UI Design: Prototipação e animações interativas
    • Figma: Conhecendo o programa
    • Product Design: métricas e ciclo de vida do produto

    Cursos

    Ver mais cursos de Gestão & Negócios
    • Canva: Criação de landing pages e e-mail marketing
    • Agilidade: como ela pode ajudar a criar um time de alta performance
    • Google Analytics 4: Extrair insights e configurar eventos
    • Management 3.0: gerencie o ambiente, não as pessoas
    • Automação de processos com n8n: Inteligência de dados para Marketing

    Cursos

    Ver mais cursos de Mobile
    • React Native: Estilização e Layouts com Flexbox e StyleSheet
    • Flutter: Graphql e suporte offline
    • Android com Gemini: Trabalhando com textos e imagens na IA
    • Dart: trabalhando com orientação a objetos
    • React Native: Dominando Listas com FlatList e ScrollView

    Carreiras

    Ver mais carreiras
    • Especialista em IA
    • Engenharia de IA
    • Ai native software engineering
    • Arquitetura de Soluções com IA
    • AI Product Design
    • Engenharia de Machine Learning
    • Desenvolvimento Front-End React
    • Engenharia de Dados
    • Cloud Security
    • Social Media Marketing
    • Engenharia de Agentes de IA

    Cursos

    • Engenharia de Prompt: Prompts Eficazes para IA Generativa
    • Claude Code: Criando sua Primeira Aplicação
    • Python: Crie sua Primeira Aplicação
    • Git e GitHub: Compartilhando e Colaborando
    • N8N: Fluxos de Trabalho Avançados

    Cursos

    • Copilot Studio: Solução Multiagentes
    • Padrões de API HTTP e Modelagem de APIs
    • Python para Análise de Dados com SQL
    • Docker: Criando e Gerenciando Containers
    • Design com IA: Otimizando o Processo Criativo

    Cursos

    • Model Context Protocol (MCP)
    • Spec-Driven Development: Dev Assistido por Agentes
    • Arquitetura de Sistemas Distribuídos com Java
    • Governança de Modelos e Reprodutibilidade
    • Pentest: Vulnerabilidades em Aplicações Web

    Escolha seu Plano

    Skills & Go

    O Skills & Go é para Tech Leads, Product Managers, estrategistas e early adopters que transformam ideias em soluções de alto impacto. Em cursos ao vivo e 100% online, você domina a nova era da IA em tempo real.

    Aulas ao vivo

    Saiba mais
    • Agentic Engineering: orquestre múltiplos agentes com confiabilidade
    • Building AI Products: transforme ideias em produtos reais com IA
    • AI Data Strategy: transforme dados em decisões estratégicas com IA

    Escolha seu Plano

    Eventos Alura

    Nossos eventos são pensados para quem quer estar à frente das mudanças em tecnologia, IA e inovação. Em experiências presenciais, você acompanha tendências e se conecta com especialistas que estão moldando o futuro.

    Próximos eventos

    • Imersão Engenharia de Software: Dev na Era da IA
    • IA Conference
    • Alura Signals
  • Talent Lab
  • IA

    Escolha seu Plano

    Conteúdos gratuitos

    Explore conteúdos gratuitos para aprender tecnologia no seu ritmo. Encontre artigos com conteúdos técnicos para aprender na prática, notícias para acompanhar as principais tendências do mercado e o Alura Labs, com análises e reflexões de especialistas sobre tecnologia, IA e carreira.

    Conteúdos gratuitos

    • Artigos
    • Notícias
    • Alura Labs
  • Para Empresas
Ver planos
Links principais
  • Planos e Promoções
  • Links complementares do menu mobile
  • Talent lab
  • Inteligência Artificial
  • Pós Graduações
  • Para Empresas
  • Sobre a Alura
  • Grupo Alun
  • Entrar
    • Home
    • Carreiras
    • Skills & Go
    • Talent Lab
    • Inteligência Artificial
    • Pós Graduações
    • Artigos
    • Sobre a Alura
    • Grupo Alun
    LoginContato
    • LinkedIn
    • Instagram
    • YouTube
    1. Página inicial
    2. Mobile
    3. Design Patterns: conheça os mais usados no Flutter

    Design Patterns: conheça os mais usados no Flutter

    17 min17 minutos de leitura

    17 min17 minutos de leitura

    Publicado: 18/11/2024

    1. O que são Design Patterns?
    2. Conclusão

    Se você já desenvolveu aplicativos com Flutter, é bem provável que tenha se deparado algumas vezes com o mesmo problema em diferentes projetos.

    Por exemplo, sabe aquele trecho de código que se repete constantemente? Ou quando você precisa modificar algo no código, como substituir uma biblioteca, e acaba tendo que alterar várias partes do sistema? Isso pode ser algo bem frustrante.

    Neste artigo, vamos abordar alguns problemas comuns no desenvolvimento com Flutter, e como você pode utilizar design patterns para resolvê-los.

    O que são Design Patterns?

    Os design patterns, ou padrões de projeto, são soluções já testadas para problemas comuns que encontramos durante o desenvolvimento de software.

    Se ao construir um aplicativo, você se depara toda vez com um problema específico, em vez de sempre tentar elaborar uma solução do zero, adotar um design patterns pode ser uma ótima opção.

    Os design patterns ficaram famosos depois que o livro "Design Patterns: Elements of Reusable Object-Oriented Software" foi publicado em 1994 pelo "Gang of Four". Desde então, se tornaram uma referência na arquitetura de software.

    Existem três grandes categorias de design patterns: os padrões criacionais, estruturais e comportamentais. E nós vamos explorar cada uma delas.

    Padrões criacionais

    Os padrões criacionais ajudam a gerenciar a criação de objetos, ou seja, em vez de criar objetos diretamente em várias partes do código, esses padrões permitem que você centralize essa tarefa em um único lugar e, se você precisar mudar a forma como os objetos são criados, só precisará fazer isso em um lugar.

    Vamos falar sobre dois dos mais conhecidos: Factory e Singleton.

    Factory

    O padrão Factory faz com que a responsabilidade de criar objetos seja de subclasses ou métodos especializados.

    Ele é útil para centralizar a lógica de criação e esconder detalhes de implementação.

    Para exemplificar, vamos explorar o código de um aplicativo de e-commerce. Nele devem ser exibidos diferentes tipos de botões (Android e iOS) de acordo com a plataforma que a pessoa usuária está utilizando. Atualmente essa verificação está sendo feita da seguinte maneira:

    import 'package:flutter/material.dart';
    import 'package:flutter/cupertino.dart';
    void main() {
      runApp(MyApp());
    }
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Plataforma de Botões',
          theme: ThemeData(primarySwatch: Colors.blue),
          home: HomeScreen(),
        );
      }
    }
    class HomeScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // Detecta a plataforma a partir do tema
        final platform = Theme.of(context).platform;
        return Scaffold(
          appBar: AppBar(
            title: Text('Renderizando Botões por Plataforma'),
          ),
          body: Center(
            child: platform == TargetPlatform.android
                ? ElevatedButton(
                    onPressed: () {
                      print("Botão do Android pressionado");
                    },
                    child: Text('Botão Android'),
                  )
                : CupertinoButton(
                    color: Colors.blue,
                    onPressed: () {
                      print("Botão do iOS pressionado");
                    },
                    child: Text('Botão iOS'),
                  ),
          ),
        );
      }
    }
    

    Este código detecta a plataforma (Android ou iOS) utilizando Theme.of(context).platform e, com base nisso, renderiza um botão específico: um ElevatedButton para Android e um CupertinoButton para iOS. Apesar de funcionar, o que teríamos de fazer se quiséssemos adicionar suporte para outra plataforma?

    A resposta é simples: precisaríamos adicionar mais uma verificação. Mas será que isso seria eficiente? Claro que não! Em um projeto grande essa verificação iria se repetir milhares de vezes, e adicionar essa nova plataforma seria muito custoso e difícil.

    Entendido o problema, que tal aplicarmos o padrão Factory para centralizar a criação desses botões? Veja o código a seguir:

    import 'package:flutter/material.dart';
    import 'package:flutter/cupertino.dart';
    abstract class Button {
      Widget render();
    }
    class AndroidButton extends Button {
      @override
      Widget render() {
        return ElevatedButton(
          onPressed: () {},
          child: Text('Botão Android'),
        );
      }
    }
    class iOSButton extends Button {
      @override
      Widget render() {
        return CupertinoButton(
          onPressed: () {},
          child: Text('Botão iOS'),
        );
      }
    }
    class ButtonFactory {
      static final Map<TargetPlatform, Button> _buttons = {
        TargetPlatform.android: AndroidButton(),
        TargetPlatform.iOS: iOSButton(),
      };
      static Button createButton(TargetPlatform platform) {
        if (_buttons.containsKey(platform)) {
          return _buttons[platform]!;
        } else {
          throw UnsupportedError('Plataforma não suportada');
        }
      }
    }
    void main() {
      runApp(MyApp());
    }
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final platform = Theme.of(context).platform;
        final button = ButtonFactory.createButton(platform);
        return MaterialApp(
          home: Scaffold(
            body: Center(
              child: button.render(),
            ),
          ),
        );
      }
    }
    

    Aqui, criamos uma classe abstrata chamada Button que define o método render(). As classes AndroidButton e iOSButton estendem Button e implementam render() de maneira específica para suas respectivas plataformas.

    A classe ButtonFactory centraliza a criação dos botões, retornando o botão correto de acordo com a plataforma que está sendo utilizada.

    Se no futuro quisermos adicionar uma nova plataforma, basta criar uma nova classe que herde de Button e adicionar a lógica dentro da factory, sem precisar alterar o restante do código.

    Singleton

    O padrão Singleton garante que uma classe tenha apenas uma instância e fornece um ponto global de acesso a essa instância.

    Ele é útil em situações em que precisamos ter uma única configuração ou estado de algum componente em toda a aplicação.

    Para ver isso na prática, vamos voltar ao aplicativo de e-commerce. Agora recebemos a missão de gerenciar um carrinho de compras globalmente, e atualmente o código está da seguinte maneira:

    class CartManager {
      CartManager() {
        print("Carrinho inicializado.");
      }
      final List<String> _items = [];
      void addItem(String item) {
        _items.add(item);
      }
      void showItems() {
        print("Itens no carrinho: ${_items.join(', ')}");
      }
    }
    void main() {
      var cartTela1 = CartManager();
      var cartTela2 = CartManager();
      // Adiciona itens em duas instâncias diferentes de carrinho
      cartTela1.addItem("Produto A");
      cartTela2.addItem("Produto B");
      cartTela1.showItems(); //tem apenas o produto A
      cartTela2.showItems(); // tem apenas o produto B
    }
    

    Neste código, ao adicionar um item no cartTela1, gostaríamos que ele também fosse adicionado automaticamente ao cartTela2.

    No entanto, a aplicação está criando múltiplas instâncias do carrinho, o que significa que teríamos que adicionar os itens manualmente em ambos, dificultando a sincronização dos carrinhos.

    Então, vamos usar o padrão Singleton para que apenas uma instância de CartManager seja criada:

    class CartManager {
      // Instância única (Singleton)
      static final CartManager _instance = CartManager._internal();
      // Construtor privado para que ninguém possa instanciar diretamente
      CartManager._internal() {
        print("Carrinho inicializado.");
      }
      // Método factory que retorna sempre a mesma instância
      factory CartManager() {
        return _instance;
      }
      // Lista de itens no carrinho
      final List<String> _items = [];
      // Método para adicionar itens
      void addItem(String item) {
        _items.add(item);
        print("Item $item adicionado ao carrinho.");
      }
      // Exibe o estado atual do carrinho
      void showItems() {
        print("Itens no carrinho: ${_items.join(', ')}");
      }
    }
    void main() {
      // Usando a instância Singleton do CartManager
      var cartTela1 = CartManager();
      var cartTela2 = CartManager();
      // Adiciona itens no carrinho usando a mesma instância
      cartTela1.addItem("Produto A");
      cartTela2.addItem("Produto B");
      // Exibe os itens no carrinho
      cartTela1.showItems();  // Saída: Itens no carrinho: Produto A, Produto B
      cartTela2.showItems();  // Saída: Itens no carrinho: Produto A, Produto B
    }
    

    A utilização de um construtor privado (_internal) e uma única “cópia” (_instance) faz com que o CartManager siga o padrão Singleton, ou seja, apenas uma instância dessa classe será criada durante toda a execução do aplicativo.

    Isso resolve o problema de múltiplas instâncias de carrinhos e mantém o estado centralizado.

    Pronto, implementamos o padrão Singleton na classe CartManager, que agora é responsável por gerenciar o estado do carrinho de compras e com isso, conseguimos resolver mais um problema da aplicação de e-commerce.

    Os padrões criacionais realmente facilitam muito a nossa vida! Mas, chegou a hora de explorar um novo padrão de design pattern, os padrões estruturais.

    Padrões Estruturais

    Os padrões estruturais ajudam a compor classes e objetos em estruturas maiores, fazendo com que os componentes possam se comunicar e funcionar juntos de maneira eficiente.

    Esses padrões são úteis para lidar com a organização de interfaces e a composição de objetos, permitindo que diferentes partes de um sistema sejam integradas de maneira mais simples.

    Dentro desse padrão, podemos destacar o Adapter e o Facade.

    Adapter

    Esse padrão é utilizado quando queremos que duas classes com interfaces incompatíveis trabalhem juntas.

    Ele age como um "tradutor", permitindo que um sistema existente seja utilizado sem modificar sua estrutura interna, apenas adaptando sua interface para atender às necessidades de uma nova interface exigida pelo cliente.

    Esse padrão é muito útil em cenários onde você está integrando novos componentes ou serviços em um sistema, mas não pode ou não quer modificar o código existente.

    O Adapter encapsula essa lógica de "tradução" e torna a integração transparente para o restante do sistema.

    Para compreender melhor esse padrão, vamos analisar um aplicativo de aluguel de imóveis.

    Nesse exemplo, temos dois serviços que retornam dados sobre uma casa: um em formato JSON e outro em formato XML:

    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:xml/xml.dart'; // Adicione a dependência da biblioteca xml no seu pubspec.yaml
    // Serviços simulando chamadas de API
    class JSONService {
      static String fetchData() {
        return '{"comodos": 5, "preco": 2500.00}';
      }
    }
    class XMLService {
      static String fetchData() {
        return '<house><comodos>4</comodos><preco>2300.00</preco></house>';
      }
    }
    class House {
      final int comodos;
      final double preco;
      House({required this.comodos, required this.preco});
      // Função para converter JSON em um objeto House
      factory House.fromJson(String jsonData) {
        final data = jsonDecode(jsonData);
        return House(
          comodos: data['comodos'],
          preco: data['preco'],
        );
      }
      // Função para converter XML em um objeto House
      factory House.fromXml(String xmlData) {
        final document = XmlDocument.parse(xmlData);
        final comodos = int.parse(_extractValueFromXml(document, 'comodos'));
        final preco = double.parse(_extractValueFromXml(document, 'preco'));
        return House(
          comodos: comodos,
          preco: preco,
        );
      }
      // Método auxiliar para extrair valores de XML
      static String _extractValueFromXml(XmlDocument document, String tagName) {
        final element = document.findAllElements(tagName).first;
        return element.text;
      }
    }
    void main() {
      // Convertendo os dados de JSON antes de passar para a tela
      final houseFromJson = House.fromJson(JSONService.fetchData());
      runApp(MyApp(houseFromJson: houseFromJson));
    }
    class MyApp extends StatelessWidget {
      final House houseFromJson;
      MyApp({required this.houseFromJson});
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'House Data Example',
          theme: ThemeData(primarySwatch: Colors.blue),
          home: HomeScreen(houseFromJson: houseFromJson),
        );
      }
    }
    class HomeScreen extends StatelessWidget {
      final House houseFromJson;
      HomeScreen({required this.houseFromJson});
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Dados da Casa'),
          ),
          body: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Dados do JSON:',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 8),
                Text('Cômodos: ${houseFromJson.comodos}', style: TextStyle(fontSize: 18)),
                Text('Preço: R\$ ${houseFromJson.preco.toStringAsFixed(2)}', style: TextStyle(fontSize: 18)),
              ],
            ),
          ),
        );
      }
    }
    

    Neste código, são exibidas informações de uma casa a partir de dados simulados em JSON e XML.

    Ele define os serviços JSONService e XMLService para fornecer dados em diferentes formatos. Já a classe House representa a casa, com cômodos e preço, e faz a conversão dos dados JSON e XML.

    Na main(), os dados (no exemplo escolhemos o JSON) são convertidos e passados para o widget MyApp, que exibe esses dados na interface HomeScreen. E a tela exibe o número de cômodos e o preço da casa com um layout simples.

    O problema é que se o JSON e XML são “variações” de services, o ideal não seria apenas entregá-los a interface sem me preocupar com verificações? Sim, esse seria o ideal, mas isso não está acontecendo porque nosso código não está adaptável.

    Além disso, se precisássemos adicionar uma nova fonte de serviço, teríamos que ajustar as verificações também.

    No momento, temos apenas um widget, mas se usássemos o serviço em vários componentes, precisaríamos fazer essas alterações manualmente em cada um deles.

    Para resolver esses problemas, podemos usar o padrão Adapter, separando assim a lógica de conversão dos dados para que a interface receba dados já convertidos, independentemente do formato original (JSON, XML, etc.). Veja o código a seguir:

    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:xml/xml.dart'; // Adicione a dependência da biblioteca xml no seu pubspec.yaml
    class House {
      final int comodos;
      final double preco;
      House({required this.comodos, required this.preco});
    }
    // Interface do Adapter
    abstract class HouseAdapter {
      House getHouse();
    }
    // Adapter para JSON
    class JSONAdapter implements HouseAdapter {
      final JSONService _jsonService = JSONService(); // Instância criada internamente
      @override
      House getHouse() {
        final data = _jsonService.fetchData();
        final decodedData = jsonDecode(data);
        return House(
          comodos: decodedData['comodos'],
          preco: decodedData['preco'],
        );
      }
    }
    // Adapter para XML
    class XMLAdapter implements HouseAdapter {
      final XMLService _xmlService = XMLService();
      @override
      House getHouse() {
        final data = _xmlService.fetchData();
        final document = XmlDocument.parse(data);
        final comodos = _extractValueFromXml(document, 'comodos');
        final preco = _extractValueFromXml(document, 'preco');
        return House(
          comodos: int.parse(comodos),
          preco: double.parse(preco),
        );
      }
      String _extractValueFromXml(XmlDocument document, String tagName) {
        final element = document.findAllElements(tagName).first;
        return element.text;
      }
    }
    class JSONService {
      String fetchData() {
        // Implementação do serviço JSON (exemplo)
        return '{"comodos": 3, "preco": 250000.0}';
      }
    }
    class XMLService {
      String fetchData() {
        // Implementação do serviço XML (exemplo)
        return '<house><comodos>3</comodos><preco>250000.0</preco></house>';
      }
    }
    void main() {
      runApp(MyApp());
    }
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Adapter Pattern Example',
          theme: ThemeData(primarySwatch: Colors.blue),
          home: HomeScreen(
            adapter: JSONAdapter(), // Trocar para XMLAdapter se necessário
          ),
        );
      }
    }
    class HomeScreen extends StatelessWidget {
      final HouseAdapter adapter;
      HomeScreen({required this.adapter});
      @override
      Widget build(BuildContext context) {
        // Obtendo os dados da casa usando o adapter
        final house = adapter.getHouse();
        return Scaffold(
          appBar: AppBar(
            title: Text('Exemplo Adapter Pattern'),
          ),
          body: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Dados da Casa:',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 16),
                Text('Cômodos: ${house.comodos}', style: TextStyle(fontSize: 18)),
                Text('Preço: R\$ ${house.preco.toStringAsFixed(2)}', style: TextStyle(fontSize: 18)),
              ],
            ),
          ),
        );
      }
    }
    

    O código apresenta a interface HouseAdapter, que serve como um intermediário para obter informações sobre casas de diferentes fontes.

    Existem duas implementações: JSONAdapter, que busca dados em formato JSON, e XMLAdapter, que faz o mesmo, mas com dados em formato XML. Ambos adaptadores transformam essas informações em objetos da classe House, que contém o número de cômodos e o preço.

    Assim, a classe HomeScreen pode usar qualquer um dos adaptadores para exibir os dados da casa de forma simples, sem se preocupar de onde essas informações estão vindo.

    Pronto, além de uma melhora significativa na estética do código, se for necessário adicionar um novo tipo de dado, basta criar um novo adapter e passar como parâmetro para nossa HomeScreen.

    Trazemos flexibilidade para nosso código e tiramos a responsabilidade de fazer conversões da nossa interface, incrível né?

    Mas calma que ainda não paramos por aqui. Agora vamos dar uma olhadinha no Facade.

    Facade

    O padrão Facade oferece uma interface simplificada para um conjunto de classes complexas, facilitando o uso de bibliotecas ou subsistemas complicados.

    Como de costume, vamos ver esse design pattern na prática, em um sistema de autenticação de pessoas usuárias. Para esse exemplo, temos o código abaixo:

    class AuthService {
      void login(String username, String password) {
        print('Usuário autenticado com sucesso.');
      }
      void logout() {
        print('Usuário desconectado.');
      }
    }
    class TwoFactorAuthService {
      void send2FACode(String username) {
        print('Enviando código de autenticação de dois fatores para $username...');
      }
      bool verify2FACode(String code) {
        print('Código de autenticação de dois fatores verificado.');
        return code == '123456'; // Exemplo de código fixo
      }
    }
    class LoginHistoryService {
      void logLoginAttempt(String username, bool success) {
        String result = success ? 'sucesso' : 'falha';
        print('Tentativa de login para $username: $result.');
      }
    }
    void main() {
      var authService = AuthService();
      var twoFactorAuthService = TwoFactorAuthService();
      var loginHistoryService = LoginHistoryService();
      String username = 'user';
      String password = 'pass';
      String twoFactorCode = '123456';
      authService.login(username, password);
      twoFactorAuthService.send2FACode(username);
      bool isTwoFactorSuccess = twoFactorAuthService.verify2FACode(twoFactorCode);
      loginHistoryService.logLoginAttempt(username, isTwoFactorSuccess);
      if (isTwoFactorSuccess) {
        print('Autenticação completa.');
      } else {
        print('Autenticação falhou.');
      }
      authService.logout();
    }
    

    Neste código, temos três serviços: AuthService para login/logout, TwoFactorAuthService para autenticação de dois fatores, e LoginHistoryService para registrar tentativas de login.

    A lógica de autenticação está espalhada pela função main(), onde os serviços são chamados manualmente.

    Todos esses passos fazem parte do processo de autenticação de login, certo? Que tal encontrarmos uma maneira de encapsular tudo isso em um só lugar?

    Assim, podemos deixar nosso código mais intuitivo e evitar ter que fazer todas essas chamadas sempre que precisarmos autenticar a pessoa usuária.

    Para encapsular todas essas responsabilidades, vamos usar o padrão Facade:

    class AuthFacade {
      final AuthService _authService = AuthService();
      final TwoFactorAuthService _twoFactorAuthService = TwoFactorAuthService();
      final LoginHistoryService _loginHistoryService = LoginHistoryService();
      void authenticate(String username, String password, String twoFactorCode) {
        _authService.login(username, password);
        _twoFactorAuthService.send2FACode(username);
        bool success = _twoFactorAuthService.verify2FACode(twoFactorCode);
        _loginHistoryService.logLoginAttempt(username, success);
        if (success) {
          print('Autenticação completa.');
        } else {
          print('Autenticação falhou.');
        }
      }
    }
    void main() {
      var authFacade = AuthFacade();
      authFacade.authenticate('user', 'pass', '123456');
    }
    

    Viu? Agora o processo de autenticação está mais simples e centralizado. Com o facade e o método authenticate, abstraímos toda a complexidade do processo de login.

    Os design patterns realmente são incríveis, não é? Mas ainda não terminamos! Agora vamos explorar os padrões comportamentais.

    Padrões Comportamentais

    Esses padrões são maneiras de organizar o código, facilitando a comunicação entre objetos e classes.

    Eles definem como os componentes interagem entre si, ajudando a resolver problemas comuns de comunicação sem que fiquem dependentes uns dos outros.

    Nós vamos estudar um dos mais famosos: o Observer!

    Observer

    O padrão Observer é muito útil em cenários onde temos componentes que dependem de um mesmo valor ou evento.

    No Flutter, isso ocorre, por exemplo, quando um dado compartilhado entre widgets muda e todos eles precisam ser atualizados.

    Sem uma estrutura adequada, podemos acabar precisando de atualizações manuais em vários pontos da nossa aplicação.

    O Observer funciona criando uma dependência entre um objeto e todos que dependem dele. Quando o estado do objeto muda, todos os dependentes são notificados automaticamente.

    Para entender melhor esse padrão, iremos implementar juntos um sistema de vídeos com canais e inscritos.

    Cada vez que o canal postar um novo vídeo, as pessoas inscritas devem ser notificadas. Podemos fazer isso dessa maneira:

    class Canal {
      String nome;
      Canal(this.nome);
      void postarVideo(String video) {
        print('$nome postou: $video');
      }
    }
    class Usuario {
      String nome;
      Usuario(this.nome);
      void receberVideo(String video) {
        print('$nome recebeu o vídeo: $video');
      }
    }
    void main() {
      var canal = Canal('Canal de Flutter');
      var usuario1 = Usuario('João');
      var usuario2 = Usuario('Maria');
      canal.postarVideo('Como usar o Observer Pattern');
      usuario1.receberVideo('Como usar o Observer Pattern');
      usuario2.receberVideo('Como usar o Observer Pattern');
      canal.postarVideo('Como melhorar seu código no Flutter');
      usuario1.receberVideo('Como melhorar seu código no Flutter');
      usuario2.receberVideo('Como melhorar seu código no Flutter');
    }
    

    Neste código, para cada vídeo postado, precisamos manualmente fazer com que todas as pessoas inscritas recebam o vídeo.

    Isso não é prático, especialmente se o número de inscritos crescer. Imagine um canal com 1000 inscritos: o trabalho seria muito maior e mais propenso a erros.

    Agora, ao aplicar o padrão Observer, podemos refatorar esse código para que o canal notifique automaticamente as pessoas inscritas quando um novo vídeo for postado:

    class Canal {
      String nome;
      List<Usuario> inscritos = [];
      Canal(this.nome);
      void inscrever(Usuario usuario) {
        inscritos.add(usuario);
      }
      void postarVideo(String video) {
        print('$nome postou: $video');
        for (var usuario in inscritos) {
          usuario.receberNotificacao(video);
        }
      }
    }
    class Usuario {
      String nome;
      Usuario(this.nome);
      void receberNotificacao(String video) {
        print('$nome recebeu a notificação do vídeo: $video');
      }
    }
    void main() {
      var canal = Canal('Canal de Flutter');
      var usuario1 = Usuario('João');
      var usuario2 = Usuario('Maria');
      canal.inscrever(usuario1);
      canal.inscrever(usuario2);
      canal.postarVideo('Como usar o Observer Pattern');
      canal.postarVideo('Como melhorar seu código no Flutter');
    }
    

    Com o Observer, o canal não precisa mais se preocupar em notificar manualmente cada pessoa usuária. Ao postar um vídeo, ele percorre a lista de inscritos e notifica todos automaticamente.

    Grandes bibliotecas, como o provider, por debaixo dos panos também aplicam os conceitos do observer.

    É interessante perceber que as soluções que estamos estudando não são nada fora da nossa realidade e ,inclusive, muitas ferramentas utilizam os design patterns.

    Banner do Guia Tático Alura sobre carreira em tecnologia, destacando o mapa da carreira tech. A imagem convida a explorar diferentes caminhos profissionais, comparar possibilidades e encontrar a próxima fase da carreira na área de tecnologia, com chamada “Explore o mapa”.

    Conclusão

    Os designers patterns são realmente ferramentas incríveis e sem dúvidas vão ajudar você a evoluir suas aplicações Flutter.

    Neste artigo, abordamos apenas alguns design patterns, mas existem muitos outros que podem resolver diferentes problemas que você encontra em seus projetos. Não deixe de pesquisar e se aprofundar sempre que necessário.

    E caso queira mergulhar em outros conteúdos do mundo mobile, deixo aqui algumas sugestões do que temos disponível na plataforma Alura:

    Artigos:

    • Design patterns: Breve introdução aos padrões de projeto
    • Flutter: escolhendo uma arquitetura para o seu projeto

    Formação:

    • Formação Flutter: desvendando arquiteturas.

    Bons estudos!

    Banner do Guia Tático Alura sobre carreira em tecnologia, destacando o mapa da carreira tech. A imagem convida a explorar diferentes caminhos profissionais, comparar possibilidades e encontrar a próxima fase da carreira na área de tecnologia, com chamada “Explore o mapa”.

    Avalie este artigo

    • Foto de Mikael Diniz

      Autor(a)

      Mikael Diniz

      Atualmente estou cursando Ciência da Computação na UFMA, sou apaixonado por programação, games, matemática, basquete, programação competitiva e LeetCode.

    Explore por tópico

    • Back-end
    • Dados
    • DevOps
    • Front-end
    • Gestão & Negócios
    • Institucionais
    • Inteligência Artificial
    • Mobile
    • UX & Design

    Leia também

    • 07/04/2025

      React Native: um guia definitivo para publicar seu aplicativo

      Ler mais
    • 08/12/2019

      Flutter: como criar um formulário

      Ler mais
    • 06/10/2022

      Como usar cores dinâmicas no Material Design 3 do Android

      Ler mais
    Ver mais conteúdos

    Inscreva-se em nossa Newsletter

    Fique por dentro de conteúdos, insights e oportunidades do universo tech. Receba novidades e lançamentos direto no seu e-mail.

    Institucional

    • Sobre nós
    • Trabalhe Conosco
    • Para Empresas
    • Para Escolas
    • Política de Privacidade
    • Compromisso de Integridade
    • Termos de Uso
    • Canal de Ética
    • Código de Ética
    • Fale Conosco
    • Documentos Institucionais
    • Status
    Institucional
    • Sobre nós
    • Trabalhe Conosco
    • Para Empresas
    • Para Escolas
    • Política de Privacidade
    • Compromisso de Integridade
    • Termos de Uso
    • Canal de Ética
    • Código de Ética
    • Fale Conosco
    • Documentos Institucionais
    • Status

    A Alura

    • Como Funciona
    • Inteligência Artificial
    • Plataforma
    • Depoimentos
    • Instrutores(as)
    • Dev em <T>
    • Luri, a Inteligência Artificial da Alura
    • IA Conference
    • Cursos Imersivos
    • Perguntas Frequentes
    A Alura
    • Como Funciona
    • Inteligência Artificial
    • Plataforma
    • Depoimentos
    • Instrutores(as)
    • Dev em <T>
    • Luri, a Inteligência Artificial da Alura
    • IA Conference
    • Cursos Imersivos
    • Perguntas Frequentes

    Conteúdos

    • Alura Cases
    • Imersão Engenharia de Software: Dev na Era da IA
    • Artigos
    • Podcasts
    • Artigos de educação corporativa
    Conteúdos
    • Alura Cases
    • Imersão Engenharia de Software: Dev na Era da IA
    • Artigos
    • Podcasts
    • Artigos de educação corporativa
    Uma Empresa do GrupoLogo Grupo Alun

    Outras empresas do Grupo Alun

    • FIAP
    • STARTSE
    • PM3
    • LUMINA

    Ao submeter seu e-mail, você concorda com a política de privacidade da Alura.

    Redes Sociais & Apps

    instagram
    youtube
    tiktok
    x
    appStore
    googlePlay
    Uma Empresa do GrupoLogo Grupo Alun

    Outras empresas do Grupo Alun

    • FIAP
    • STARTSE
    • PM3
    • LUMINA

    Ao submeter seu e-mail, você concorda com a política de privacidade da Alura.

    Redes Sociais & Apps

    instagram
    youtube
    tiktok
    x
    appStore
    googlePlay
    Alura