Clean Architecture Implementation in Flutter

Clean Architecture Implementation in Flutter

A quick guide on how to implement Clean Architecture in Flutter

Introduction

Clean Architecture is a software design philosophy that emphasizes separation of concerns and maintainability by dividing the software into layers with distinct responsibilities. In the context of Flutter, Clean Architecture can be a powerful approach to create scalable, testable, and maintainable applications.

This technical writing will guide you through the implementation of Clean Architecture in a Flutter project, covering key concepts, layers, and code examples.

Key Concepts of Clean Architecture

Clean Architecture comprises the following layers:

  1. Entities: Represent the core business logic and contain business rules and data structures.

  2. Use Cases (Interactors): Define application-specific business rules and orchestrate the flow of data between the entities and the outer layers.

  3. Interface Adapters: Convert data between the use cases and the external systems, such as the UI, databases, or external services. Includes presenters, controllers, and gateways.

  4. Frameworks and Drivers: External frameworks, tools, and libraries, including the UI framework (Flutter), databases, and external services.

The primary goal is to keep the inner layers independent of the outer layers, allowing for easier maintenance, testing, and future changes.

Project Structure

Let's create a simple Flutter project with a Clean Architecture structure. Consider a basic task management app.

project_root/
|-- lib/
|   |-- core/
|   |   |-- entities/
|   |   |-- use_cases/
|   |   |   |-- task_use_case.dart
|   |   |-- repositories/
|   |       |-- task_repository.dart
|   |
|   |-- data/
|   |   |-- repositories_impl/
|   |   |   |-- task_repository_impl.dart
|   |
|   |-- presentation/
|   |   |-- screens/
|   |   |   |-- task_list_screen.dart
|   |   |   |-- add_task_screen.dart
|   |   |-- presenters/
|   |       |-- task_list_presenter.dart
|   |       |-- add_task_presenter.dart
|   |
|   |-- app.dart
|-- main.dart

Entities

// lib/core/entities/task.dart

class Task {
  final String id;
  final String title;
  final bool isCompleted;

  Task({required this.id, required this.title, required this.isCompleted});
}

Use Cases

// lib/core/use_cases/task_use_case.dart

import 'package:your_project/core/entities/task.dart';

abstract class TaskUseCase {
  Future<List<Task>> getTasks();
  Future<void> addTask(Task task);
}

Repositories

// lib/core/repositories/task_repository.dart

import 'package:your_project/core/entities/task.dart';

abstract class TaskRepository {
  Future<List<Task>> getTasks();
  Future<void> addTask(Task task);
}

Repository Implementation

// lib/data/repositories_impl/task_repository_impl.dart

import 'package:your_project/core/entities/task.dart';
import 'package:your_project/core/repositories/task_repository.dart';

class TaskRepositoryImpl implements TaskRepository {
  @override
  Future<List<Task>> getTasks() async {
    // Implementation to fetch tasks from a data source (e.g., database)
    // ...
  }

  @override
  Future<void> addTask(Task task) async {
    // Implementation to add a task to a data source (e.g., database)
    // ...
  }
}

Presenters

// lib/presentation/presenters/task_list_presenter.dart

import 'package:your_project/core/entities/task.dart';
import 'package:your_project/core/use_cases/task_use_case.dart';

class TaskListPresenter {
  final TaskUseCase taskUseCase;

  TaskListPresenter(this.taskUseCase);

  Future<List<Task>> getTasks() async {
    return await taskUseCase.getTasks();
  }
}

UI Screens

// lib/presentation/screens/task_list_screen.dart

import 'package:flutter/material.dart';
import 'package:your_project/presentation/presenters/task_list_presenter.dart';

class TaskListScreen extends StatefulWidget {
  final TaskListPresenter presenter;

  TaskListScreen({required this.presenter});

  @override
  _TaskListScreenState createState() => _TaskListScreenState();
}

class _TaskListScreenState extends State<TaskListScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Task List')),
      body: FutureBuilder(
        future: widget.presenter.getTasks(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
          } else if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          } else {
            List<Task> tasks = snapshot.data as List<Task>;
            return ListView.builder(
              itemCount: tasks.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(tasks[index].title),
                  // ... other task details
                );
              },
            );
          }
        },
      ),
    );
  }
}

Wiring It All Together

// lib/app.dart

import 'package:flutter/material.dart';
import 'package:your_project/data/repositories_impl/task_repository_impl.dart';
import 'package:your_project/presentation/presenters/task_list_presenter.dart';
import 'package:your_project/presentation/screens/task_list_screen.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Clean Architecture Flutter',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TaskListScreen(
        presenter: TaskListPresenter(
          TaskRepositoryImpl(),
        ),
      ),
    );
  }
}

Conclusion

This example provides a foundation for implementing Clean Architecture in a Flutter project. The separation of concerns allows for easier testing and maintenance, and the codebase can be extended with new features without affecting existing components. It's crucial to adapt the structure based on the project's specific requirements and complexities.

Did you find this article valuable?

Support Flutter Aware by becoming a sponsor. Any amount is appreciated!