Building a Flutter Project with Atomic Design Concept
Sun, March 2025 00:06:19
In app development, keeping the code structure clean, reusable, and easy to maintain is crucial. One popular design approach is Atomic Design. This concept was first introduced by Brad Frost and has been widely adapted to various frameworks, including Flutter. This article will dive deep into how to implement the Atomic Design concept in Flutter, complete with code examples and step-by-step explanations.
What is Atomic Design?
Atomic Design is a method of building UI gradually by breaking components into smaller, reusable parts. This concept is divided into five main levels:
- Atom — The smallest component that cannot be broken down further, such as text, buttons, icons, or input fields.
- Molecule — A combination of several atoms that work together, like an input field with an icon.
- Organism — A combination of molecules and atoms that form a larger UI section, like a login form.
- Template — A page structure without specific data, just the layout.
- Page — A complete page containing components filled with data.
In Flutter implementation, we’ll mainly focus on Atom, Molecule, and Organism for more efficiency and reusability.
Folder Structure
To keep the project organized, let’s start by setting up a folder structure that matches the Atomic Design concept:
/lib
└── components
├── atoms
│ ├── custom_button.dart
│ ├── custom_text.dart
│ └── custom_icon.dart
│
├── molecules
│ └── custom_textfield.dart
│
└── organisms
└── login_form.dart
└── pages
└── login_page.dart
With this structure, each design level has its own place. This makes it easier to find or modify specific components.
Creating Atom Components
Let’s start by creating basic components in the atoms folder. These components will be frequently used across the app.
custom_button.dart
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
const CustomButton({
Key? key,
required this.text,
required this.onPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
child: Text(
text,
style: TextStyle(fontSize: 16),
),
);
}
}
This component is an atom that only takes text and an onPressed function. We can use it in many places without rewriting.
Creating Molecule Components
Next, let’s create molecules — combinations of several atoms that form more complex components. For example, a TextField with a visibility icon for passwords.
custom_textfield.dart
import 'package:flutter/material.dart';
class CustomTextField extends StatefulWidget {
final String label;
final bool isPassword;
final TextEditingController controller;
const CustomTextField({
Key? key,
required this.label,
required this.controller,
this.isPassword = false,
}) : super(key: key);
@override
_CustomTextFieldState createState() => _CustomTextFieldState();
}
class _CustomTextFieldState extends State<CustomTextField> {
bool _isObscured = true;
@override
Widget build(BuildContext context) {
return TextField(
controller: widget.controller,
obscureText: widget.isPassword ? _isObscured : false,
decoration: InputDecoration(
labelText: widget.label,
suffixIcon: widget.isPassword
? IconButton(
icon: Icon(
_isObscured ? Icons.visibility_off : Icons.visibility,
),
onPressed: () {
setState(() {
_isObscured = !_isObscured;
});
},
)
: null,
border: OutlineInputBorder(),
),
);
}
}
This molecule is more complex because it has its own state logic. We can use it in forms that need input with icons.
Creating Organism Components
Now let’s combine molecules and atoms into a larger organism. For example, a complete login form.
login_form.dart
import 'package:flutter/material.dart';
import '../atoms/custom_button.dart';
import '../molecules/custom_textfield.dart';
class LoginForm extends StatelessWidget {
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Column(
children: [
CustomTextField(
label: "Email",
controller: emailController,
),
SizedBox(height: 16),
CustomTextField(
label: "Password",
controller: passwordController,
isPassword: true,
),
SizedBox(height: 24),
CustomButton(
text: "Login",
onPressed: () {
print("Logging in with: \${emailController.text}");
},
),
],
);
}
}
Creating the Main Page
Now, let’s create the main page that calls the LoginForm organism.
login_page.dart
import 'package:flutter/material.dart';
import '../components/organisms/login_form.dart';
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login Page')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(child: LoginForm()),
),
);
}
}
Conclusion
By implementing the Atomic Design concept in Flutter, we can:
- Build more reusable and modular components.
- Speed up development by assembling components.
- Simplify code maintenance.
- Help development teams better understand code structure.
Atomic Design keeps the code cleaner and more professional. Are you interested in applying this to your Flutter project? Happy coding, and good luck building a scalable and maintainable app!