Componentes Reutilizáveis no Flutter: Use de Forma Eficiente

Já se pegou escrevendo o mesmo código de interface de usuário repetidamente no Flutter? Se a resposta for sim, chegou a hora de conhecer os componentes reutilizáveis – uma das melhores formas de escrever um código limpo, sustentável e escalável. Eles são a chave para otimizar seu fluxo de trabalho e garantir que seus aplicativos permaneçam organizados e fáceis de manter a longo prazo.

Por que usar Componentes reutilizáveis em Flutter?

No Flutter, widgets são a base de tudo e podem ser projetados para serem reutilizáveis. Em vez de duplicar código, você pode criar widgets personalizados e classes de serviço que podem ser usados em diferentes partes do seu app. Essa abordagem não só economiza tempo, mas também simplifica a manutenção e melhora a escalabilidade do seu projeto.

Benefícios dos componentes reutilizáveis

Menos duplicação de código: defina uma vez, use em qualquer lugar. Ao criar componentes reutilizáveis, você evita a repetição desnecessária de código, tornando seu projeto mais limpo e organizado. Imagine a facilidade de modificar um componente em um único lugar e ver as alterações refletidas em todo o aplicativo!

Manutenção facilitada: corrija ou atualize em um local e isso se reflete em todos os lugares. A manutenção se torna muito mais simples quando você utiliza componentes reutilizáveis. Corrigir um bug ou adicionar uma nova funcionalidade em um componente afeta automaticamente todas as instâncias desse componente no seu app, economizando tempo e esforço.

Melhor escalabilidade: seu app cresce sem se transformar em uma bagunça de código repetido. A escalabilidade é um dos maiores benefícios dos componentes reutilizáveis. Seu aplicativo pode crescer e evoluir sem se tornar um emaranhado de código repetido, mantendo a estrutura organizada e fácil de entender.

Além da interface do usuário: onde mais você pode aplicar isso?

Serviços de API: centralizando chamadas de API para melhor gerenciamento. Centralizar as chamadas de API em um cliente reutilizável facilita o gerenciamento e a manutenção do seu código. Em vez de espalhar as chamadas de API por todo o app, você tem um ponto centralizado para lidar com todas as solicitações.

Gerenciamento de estado: usando soluções como Provider ou Bloc para evitar repetição desnecessária de lógica. Utilizar soluções de gerenciamento de estado como Provider ou Bloc ajuda a evitar a repetição desnecessária de lógica. Essas ferramentas permitem que você compartilhe o estado do seu aplicativo entre diferentes widgets, garantindo que a lógica seja consistente e reutilizável.

Entradas de formulário e botões personalizados: padronizando UI components para consistência. Padronizar os UI components, como entradas de formulário e botões personalizados, garante a consistência visual e funcional do seu aplicativo. Criar widgets reutilizáveis para esses elementos facilita a manutenção e garante que a interface do usuário permaneça uniforme em todo o app.

Para quem busca soluções de design intuitivas, vale a pena conferir como a Apple está implementando uma atualização significativa em suas interfaces.

Exemplo 1: Um cliente API reutilizável (chamadas API centralizadas) 🌐

Em vez de escrever chamadas de API várias vezes em diferentes partes do seu app, você pode criar um cliente API genérico para lidar com todas as solicitações de forma estruturada.

Passo 1: Crie um cliente API reutilizável.

import 'package:dio/dio.dart';

class ApiClient {
  final Dio _dio = Dio(BaseOptions(baseUrl: "https://jsonplaceholder.typicode.com"));

  Future<T> get<T>(String endpoint) async {
    final response = await _dio.get(endpoint);
    return response.data as T;
  }

  Future<T> post<T>(String endpoint, dynamic data) async {
    final response = await _dio.post(endpoint, data: data);
    return response.data as T;
  }

  Future<T> put<T>(String endpoint, dynamic data) async {
    final response = await _dio.put(endpoint, data: data);
    return response.data as T;
  }

  Future<T> delete<T>(String endpoint) async {
    final response = await _dio.delete(endpoint);
    return response.data as T;
  }
}

Passo 2: Use o cliente API no seu app. Em vez de escrever manualmente chamadas API em todos os lugares, agora você chama os métodos com uma única linha:

void fetchUsers() async {
  List<dynamic> users = await ApiClient().get<List<dynamic>>("/users");
  print(users);
}

void createUser() async {
  Map<String, dynamic> user = await ApiClient().post<Map<String, dynamic>>(
    "/users",
    {"name": "Coder Coder", "email": "[email protected]"},
  );
  print(user);
}

Benefícios desta abordagem.

  • Reusabilidade: uma classe lida com todas as solicitações API.
  • Flexibilidade: suporta vários tipos de solicitação (GET, POST, PUT, DELETE).
  • Manutenção: se você precisar modificar a lógica API (por exemplo, adicionar cabeçalhos, interceptores), atualize um arquivo em vez de vários locais.

Para quem gosta de estar sempre atualizado, a Microsoft aprimora TypeScript, aumentando a eficiência dos desenvolvedores.

Exemplo 2: Um Widget de botão reutilizável 🖱️

Em vez de estilizar e definir botões repetidamente, crie um widget personalizado que pode ser reutilizado em qualquer lugar do seu app.

Passo 1: Crie um componente de botão reutilizável.

import 'package:flutter/material.dart';

class CustomButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;
  final Color color;

  const CustomButton({
    Key? key,
    required this.text,
    required this.onPressed,
    this.color = Colors.blue,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(backgroundColor: color),
      onPressed: onPressed,
      child: Text(text, style: const TextStyle(color: Colors.white)),
    );
  }
}

Passo 2: Use o botão reutilizável na sua UI.

CustomButton(
  text: "Click Me",
  onPressed: () {
    print("Button Clicked!");
  },
),

Você pode personalizar o componente como quiser, adicionar qualquer propriedade; o código anterior é apenas um código simples.

Benefícios desta abordagem:

  • Consistência: garante que os botões tenham a mesma aparência em todo o app.
  • Facilidade de manutenção: atualize os estilos de botão em um só lugar.
  • Menos código repetido: basta passar texto, cor e função quando necessário.

Para mais novidades do mundo da tecnologia, veja que a Xiaomi anuncia data de lançamento do HyperOS 2.2.

Considerações finais 💡

Ao tornar seus UI components e serviços API reutilizáveis, você:

  • Escreve menos código, mantendo uma estrutura melhor.
  • Garante a consistência em todo o seu app.
  • Facilita as atualizações futuras, pois as mudanças acontecem em um só lugar.

Veja algumas fotos de UI com componente reutilizável.

A list of users using the Mock API and use a **reusable component** like **buttons**and **cards**

A list of users using the Mock API and use a **reusable component** like **buttons** and **cards**

Aqui está o uso de códigos no exemplo de fotos. Código de botão personalizado.

import 'package:flutter/material.dart';

class CustomButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;
  final Color color;
  final double borderRadius;
  final EdgeInsetsGeometry padding;
  final TextStyle? textStyle;

  const CustomButton({
    Key? key,
    required this.text,
    required this.onPressed,
    this.color = Colors.blue,
    this.borderRadius = 8.0,
    this.padding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
    this.textStyle,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        backgroundColor: color,
        padding: padding,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(borderRadius),
        ),
      ),
      onPressed: onPressed,
      child: Text(
        text,
        style: textStyle ?? const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
      ),
    );
  }
}

Código do cartão de usuário personalizado.

import 'package:flutter/material.dart';

class UserCard extends StatelessWidget {
  final Map<String, dynamic> user;
  final VoidCallback? onTap;
  final Color cardColor;
  final double elevation;
  final double borderRadius;

  const UserCard({
    Key? key,
    required this.user,
    this.onTap,
    this.cardColor = Colors.white,
    this.elevation = 2.0,
    this.borderRadius = 8.0,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: elevation,
      color: cardColor,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(borderRadius),
      ),
      margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(borderRadius),
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  CircleAvatar(
                    backgroundColor: Colors.blue.shade100,
                    child: Text(
                      user['name'][0].toUpperCase(),
                      style: const TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
                    ),
                  ),
                  const SizedBox(width: 16.0),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          user['name'],
                          style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
                        ),
                        const SizedBox(height: 4.0),
                        Text(
                          user['email'],
                          style: TextStyle(fontSize: 14.0, color: Colors.grey.shade700),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
              if (user['company'] != null) ...[  
                const SizedBox(height: 12.0),
                Text(
                  'Company: ${user['company']['name']}',
                  style: TextStyle(fontSize: 14.0, color: Colors.grey.shade800),
                ),
              ],
              if (user['phone'] != null) ...[  
                const SizedBox(height: 8.0),
                Text(
                  'Phone: ${user['phone']}',
                  style: TextStyle(fontSize: 14.0, color: Colors.grey.shade800),
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }
}

Código da classe de cliente API personalizada.

import 'package:dio/dio.dart';

class ApiClient {
  final Dio _dio = Dio(BaseOptions(baseUrl: "https://jsonplaceholder.typicode.com"));

  Future<T> get<T>(String endpoint) async {
    try {
      final response = await _dio.get(endpoint);
      return response.data as T;
    } catch (e) {
      throw Exception("Failed to load data");
    }
  }

  Future<T> post<T>(String endpoint, dynamic data) async {
    try {
      final response = await _dio.post(endpoint, data: data);
      return response.data as T;
    } catch (e) {
      throw Exception("Failed to create data");
    }
  }

  Future<T> put<T>(String endpoint, dynamic data) async {
    try {
      final response = await _dio.put(endpoint, data: data);
      return response.data as T;
    } catch (e) {
      throw Exception("Failed to update data");
    }
  }

  Future<T> delete<T>(String endpoint) async {
    try {
      final response = await _dio.delete(endpoint);
      return response.data as T;
    } catch (e) {
      throw Exception("Failed to delete data");
    }
  }
}

Código de UI.

import 'package:flutter/material.dart';
import 'package:test_sentry/api_client.dart';
import 'package:test_sentry/widgets/user_card.dart';
import 'package:test_sentry/widgets/custom_button.dart';

class UserListScreen extends StatefulWidget {
  @override
  _UserListScreenState createState() => _UserListScreenState();
}

class _UserListScreenState extends State<UserListScreen> {
  late Future<List<dynamic>> _users;
  bool _isGridView = false;

  @override
  void initState() {
    super.initState();
    _users = ApiClient().get<List<dynamic>>("/users");
  }

  void _refreshUsers() {
    setState(() {
      _users = ApiClient().get<List<dynamic>>("/users");
    });
  }

  void _toggleViewMode() {
    setState(() {
      _isGridView = !_isGridView;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Users List'),
        actions: [
          IconButton(
            icon: Icon(_isGridView ? Icons.list : Icons.grid_view),
            onPressed: _toggleViewMode,
          ),
        ],
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                CustomButton(
                  text: 'Refresh Data',
                  onPressed: _refreshUsers,
                  color: Colors.green,
                ),
                CustomButton(
                  text: 'Create User',
                  onPressed: () {
                    // Demonstrate API client post method
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text('Creating user...')),
                    );

                    ApiClient().post<Map<String, dynamic>>(
                      "/users",
                      {"name": "New User", "email": "[email protected]"},
                    ).then((response) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('User created with ID: ${response["id"]}')),
                      );
                    }).catchError((error) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('Error: $error')),
                      );
                    });
                  },
                  color: Colors.blue,
                ),
              ],
            ),
          ),
          Expanded(
            child: FutureBuilder<List<dynamic>>(
              future: _users,
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return Center(child: CircularProgressIndicator());
                } else if (snapshot.hasError) {
                  return Center(child: Text('Error: ${snapshot.error}'));
                } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
                  return Center(child: Text('No data available.'));
                } else {
                  var users = snapshot.data!;

                  if (_isGridView) {
                    return GridView.builder(
                      padding: const EdgeInsets.all(8.0),
                      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: 2,
                        childAspectRatio: 0.8,
                        crossAxisSpacing: 10,
                        mainAxisSpacing: 10,
                      ),
                      itemCount: users.length,
                      itemBuilder: (context, index) {
                        return UserCard(
                          user: users[index],
                          onTap: () {
                            ScaffoldMessenger.of(context).showSnackBar(
                              SnackBar(content: Text('Selected: ${users[index]["name"]}')),
                            );
                          },
                        );
                      },
                    );
                  } else {
                    return ListView.builder(
                      itemCount: users.length,
                      itemBuilder: (context, index) {
                        return UserCard(
                          user: users[index],
                          onTap: () {
                            ScaffoldMessenger.of(context).showSnackBar(
                              SnackBar(content: Text('Selected: ${users[index]["name"]}')),
                            );
                          },
                        );
                      },
                    );
                  }
                }
              },
            ),
          ),
        ],
      ),
    );
  }
}

Este conteúdo foi auxiliado por Inteligência Artificiado, mas escrito e revisado por um humano.
Via Dev.to

Leave a Comment