Have you ever found yourself writing the same UI code repeatedly in Flutter? If so, it's time to embrace Reusable Components—one of the best ways to write clean, maintainable, and scalable code.

Why Use Reusable Components?

In Flutter, Widgets are the foundation of everything, and they can be designed to be reusable. Instead of duplicating code, you can create custom widgets and service classesthat can be used across different parts of your app.

Benefits of Reusable Components

Less Code Duplication – Define once, use anywhere.
Easier Maintenance– Fix or update in one place, and it's reflected everywhere.
Better Scalability – Your app grows without turning into a mess of repeated code.

Beyond UI: Where Else Can You Apply This?

✔️ API Services – Centralizing API calls for better management.
✔️State Management – Using solutions like Provider or Bloc to avoid unnecessary logic repetition.
✔️ Form Inputs & Custom Buttons – Standardizing UI components for consistency.

Example 1: A Reusable API Client (Centralized API Calls) 🌐

Instead of writing*API calls multiple times* in different parts of your app, you can create a generic API client to handle all requests in a structured way.
*📌 Step 1: Create a Reusable API Client
*

import 'package:dio/dio.dart';

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

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

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

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

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

📌 Step 2: Use the API Client in Your App
Instead of manually writing API calls everywhere, you now call the methods with a single line:

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

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

🚀 Benefits of This Approach
**
✅ **Reusability
– One class handles all API requests.
Flexibility – Supports multiple request types

(GET, POST, PUT, DELETE).
Maintainability– If you need to modify API logic (e.g., add headers, interceptors), update one file instead of multiple places.

Example 2: A Reusable Button Widget 🖱️
Instead of styling and defining buttons repeatedly, create a custom widget that can be reused anywhere in your app.

📌 Step 1: Create a Reusable Button Component

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)),
    );
  }
}

📌 Step 2: Use the Reusable Button in Your UI

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

You can customize the component as you want add any property the previous code is just a *simple code * .

🚀 Benefits of This Approach
Consistency– Ensures buttons look the same across the app.
Ease of Maintenance – Update button styles in one place.
Less Repeated Code – Just pass text, color, and function when needed.

Final Thoughts 💡

By making your UI components and API services reusable, you:
Write less code while maintaining better structure.
Ensure consistency across your app.
Make future updates easier, as changes happen in one place.

I will show some photos of UI **with **reusable component

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**

Here is the codes usage in the photos example
Custom Button Code

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),
      ),
    );
  }
}

_Custom User Card Code
_

import 'package:flutter/material.dart';

class UserCard extends StatelessWidget {
  final Map 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),
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }
}

Custom API Client Class Code

import 'package:dio/dio.dart';

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

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

  Future post(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 put(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 delete(String endpoint) async {
    try {
      final response = await _dio.delete(endpoint);
      return response.data as T;
    } catch (e) {
      throw Exception("Failed to delete data");
    }
  }
}

_Code of 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 {
  late Future> _users;
  bool _isGridView = false;

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

  void _refreshUsers() {
    setState(() {
      _users = ApiClient().get>("/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>(
                      "/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>(
              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"]}')),
                            );
                          },
                        );
                      },
                    );
                  }
                }
              },
            ),
          ),
        ],
      ),
    );
  }
}

💬** How do you manage reusability in your Flutter projects? Let’s discuss in the comments! 🚀**
Want more Flutter tips?
🔹 Join my Telegram channel for more Flutter guides: Telegram Link
🔹 Follow my Facebook page for daily coding insights: Facebook Link