Os 5 padrões de projeto para todo engenheiro do Flutter

Tempo de leitura: 4 minutes

Aumente o nível de suas habilidades de engenharia.

O Flutter, com seu hot reload e toneladas de widgets, é um sonho para a criação de UIs bonitas e envolventes. Mas, à medida que nossos aplicativos crescem, cresce também a complexidade de gerenciar o fluxo de dados e manter seu código organizado.

Lembre-se de que é sempre aconselhável evitar o débito técnico que, na maioria das vezes, acontece quando a base de código do seu aplicativo começa a atingir milhares de linhas. É aqui que entram os padrões de design – eles são soluções testadas em batalha para desafios comuns de desenvolvimento de software que os engenheiros enfrentam desde o início da engenharia e do design de sistemas.

Pense nos padrões de design como projetos arquitetônicos para o seu código. Eles fornecem uma abordagem estruturada para a criação de objetos, comunicação e relacionamentos, o que leva a mais:

  • Reusabilidade: Não reinvente a roda! Os padrões de design oferecem soluções predefinidas que podem ser adaptadas em diferentes partes do seu aplicativo. Fácil de usar 😊.
  • Capacidade de manutenção: Um código limpo e bem estruturado é mais fácil de entender, modificar e depurar, economizando tempo e frustração a longo prazo 🚀.
  • Testabilidade: Os padrões de design geralmente promovem a separação de preocupações, facilitando o isolamento e o teste de componentes individuais do seu aplicativo ✅.

Vamos dar uma olhada em alguns dos padrões de projeto mais comuns para os engenheiros do Flutter, juntamente com exemplos práticos para ilustrar seu poder:

 

Padrão Singleton

Já precisou de um local central para armazenar dados de todo o aplicativo, como preferências do usuário ou um gerenciador de temas? O padrão Singleton garante que exista apenas uma instância de uma classe em todo o aplicativo. Imagine uma única classe “SettingsManager” que controla o tema do aplicativo. Qualquer parte da interface do usuário pode acessar esse gerenciador para recuperar as configurações atuais do tema e estilizar seus widgets de acordo com elas. Para aplicativos que usam GraphQL, imagine um único cliente GraphQL que você possa usar para executar consultas e mutações. Um exemplo ainda mais comum é uma conexão de websocket, seu aplicativo quase sempre precisará de apenas uma.

É nesse ponto que esse padrão pode ser utilizado.

class SettingsManager {
  static final SettingsManager _instance = SettingsManager._internal();

  factory SettingsManager() => _instance;

  SettingsManager._internal();

  ThemeData _themeData = ThemeData.light();

  void switchTheme() {
    _themeData = _themeData.brightness == Brightness.light ? ThemeData.dark() : ThemeData.light();
  }

  ThemeData getTheme() => _themeData;
}

 

Padrão Factory Method

Quando você precisa gerar dinamicamente elementos de interface do usuário com base em dados, o padrão Factory Method fornece uma interface para a criação de objetos sem especificar a classe exata antecipadamente. Isso permite maior flexibilidade – por exemplo, uma classe de fábrica poderia criar diferentes tipos de botões (primário, secundário) com base nos dados que recebe.

Para os amantes de esportes, pense em um painel que mostre as apostas que um usuário fez, mas, dependendo do resultado do jogo, o usuário pode ver um widget de ganho ou perda.

abstract class ButtonFactory {
  Widget createButton(String text);
}

class PrimaryButtonFactory implements ButtonFactory {
  @override
  Widget createButton(String text) => ElevatedButton(onPressed: null, child: Text(text));
}

class SecondaryButtonFactory implements ButtonFactory {
  @override
  Widget createButton(String text) => TextButton(onPressed: null, child: Text(text));
}

// Uso
ButtonFactory buttonFactory = (isPrimary) => isPrimary ? PrimaryButtonFactory() : SecondaryButtonFactory();
Widget myButton = buttonFactory(true).createButton("Click Me"); // Cria um botão primário

Padrão Provider

O padrão provider é uma solução leve de gerenciamento de estado, ideal para o gerenciamento eficiente de dados. Ele aproveita o InheritedWidget para propagar as alterações de dados pela árvore de widgets.

Pense em uma lista global de despesas do usuário para um aplicativo de despesas. O padrão Provider permite que você crie um único ExpenseProvider que mantém o estado do contador e “escuta” as alterações. Qualquer widget na árvore que precise exibir as despesas pode acessá-lo por meio do Provider.

class MyExpensesProvider with ChangeNotifier {
  DateTime startDate = DateTime.now().subtract(const Duration(days: 30));
  DateTime endDate = DateTime.now();

  bool isLoading = false;
  String? error;

  List<Expense> expsenses = [];

  MyExpensesProvider() {
    getExpenses();
  }

  getExpenses() async {
    
  }

}

 

Padrão de composição

A principal força do Flutter está em seu sistema de widgets compostáveis. Isso se alinha perfeitamente com o padrão de composição, que promove a criação de interfaces de usuário complexas por meio da montagem de widgets menores e reutilizáveis.

Esse padrão promove a reutilização, a flexibilidade e a capacidade de teste.

class RoundedButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;

  const RoundedButton({required this.text, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: onPressed,
      child: Container(
        padding: EdgeInsets.all(16.0),
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(10.0),
        ),
        child: Text(text, style: TextStyle(color: Colors.white)),
      ),
    );
  }
}

// Uso
Widget myScreen = Scaffold(
  body: Center(
    child: RoundedButton(text: 'Click Me', onPressed: () => print('Button Pressed')),
  ),
);

 

Padrão Bloc

O padrão Bloc oferece uma abordagem estruturada para o gerenciamento de estado, separando a interface do usuário (camada de apresentação) da lógica comercial (busca de dados, cálculos etc.). Isso resulta em um código mais limpo, mais fácil de manter e altamente testável.

Ele inclui blocos que manipulam eventos, atualizam e emitem estado, eventos que representam ações do aplicativo, estado que são dados contidos em um aplicativo e o BlocProvider para gerenciar o ciclo de vida do bloco e fornecê-lo a widgets secundários. Para aqueles que estão familiarizados com TypeScript e React, isso é muito semelhante ao React Redux em vários aspectos.

@immutable
abstract class CounterEvent {}

// Eventos
class IncrementEvent extends CounterEvent {}

class DecrementEvent extends CounterEvent {}

// Estado
class CounterState {
  final int counter;

  CounterState(this.counter);
}

// Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0));

  @override
  Stream<CounterState> mapEventToState(CounterEvent event) async* {
    if (event is IncrementEvent) {
      yield CounterState(state.counter + 1);
    } else if (event is DecrementEvent) {
      yield CounterState(state.counter - 1);
    }
  }
}

// Usage (in UI)
Widget build(BuildContext context) {
  return BlocProvider(
    create: (context) => CounterBloc(),
    child: Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Column(
        mainAxisAlignment: MainCenter,
        children: [
          BlocBuilder<CounterBloc, CounterState>(
            builder: (context, state) => Text('Count: ${state.counter}'),
          ),
          Row(
            mainAxisAlignment: MainSpaceEvenly,
            children: [
              ElevatedButton(
                onPressed: () => BlocProvider.of<CounterBloc>(context).add(IncrementEvent()),
                child: Icon(Icons.add),
              ),
              ElevatedButton(
                onPressed: () => BlocProvider.of<CounterBloc>(context).add(DecrementEvent()),
                child: Icon(Icons.remove),
              ),
            ],
          ),
        ],
      ),
    ),
  );
}

 

 

Conclusão

A maioria dos engenheiros móveis que trabalham em aplicativos móveis não está tão familiarizada com os padrões de design e o objetivo deste guia foi oferecer um ponto de partida para eles, bem como dar aos engenheiros seniores uma atualização sobre os padrões mais comuns.

Eu o criei para o Flutter porque os documentos do Flutter não oferecem exatamente esses padrões de maneira explícita, como o Swift faz, por exemplo.

Boa construção 🚀 e, como sempre, Long Live The Code.