Flutter Clean Architecture: Domain Layer

October 29, 2024
Flutter Clean Architecture: Domain Layer
Flutter Clean Architecture: Domain Layer
The Domain Layer is the heart of Clean Architecture. It contains the business logic and the core functionalities of your application. This layer is independent of any external frameworks, databases, or user interfaces, making it reusable and easy to test.

Table of Contents

Entities

Entities are business object of your application. Entities are extended with equatable package which override the == operator as well as hashCode so that we don’t have to waste our time writing lots of boilerplate code.

				
					import 'package:equatable/equatable.dart';

class Todo extends Equatable {
  final int userId;
  final int id;
  final String title;
  final bool completed;

  const Todo({
    required this.userId,
    required this.id,
    required this.title,
    required this.completed,
  });
  
  @override
  List<Object?> get props => [userId, id, title, completed];
}
				
			

Here Todo class is extended with Equatable. Equality and hash code are based on fields provided in props property.

You can also use freezed package for your Entity.

This will create a file named todo.freezed.dart with all the required methods for your Todo class, including copyWith, hashCode, ==, and more.

Your Todo class will now leverage the features provided by freezed, making it easier to manage immutable data classes in your Flutter app.

				
					dependencies:
  freezed_annotation: ^2.5.7

dev_dependencies:
  build_runner: ^2.4.13
  freezed: ^2.4.4

				
			
				
					import 'package:freezed_annotation/freezed_annotation.dart';

part 'todo.freezed.dart';

@freezed
class Todo with _$Todo {
  const factory Todo({
    required int userId,
    required int id,
    required String title,
    required bool completed,
  }) = _Todo;
}

				
			

This will create a file named todo.freezed.dart with all the required methods for your Todo class, including copyWith, hashCode, ==, and more.

Your Todo class will now leverage the features provided by freezed, making it easier to manage immutable data classes in your Flutter app.

Repositories Interface

Repositories in domain layer provides abstraction layer between domain layer and data layer. They have different functions that can be used to retrieve and manipulate data. The implementation of these functions are done in data layer. 

				
					import 'package:flutter_clean_architecture/feature/todo/domain/entity/todo.dart';

abstract class TodoRepository {
  Future<List<Todo>> getTodoList();
}
				
			

Here we create abstract class TodoRepository where we provide all necessary functions.

Use Cases

Use Cases are actions that takes certain input and gives us output based on the input. Each use case represents a single action or functionality within the application. Use case should be free of any external dependencies.

				
					import 'package:flutter_clean_architecture/feature/todo/domain/entity/todo.dart';
import 'package:flutter_clean_architecture/feature/todo/domain/repository/todo_repository.dart';
import 'package:injectable/injectable.dart';

@LazySingleton()
class GetTodoListUsecase {
  final TodoRepository _repository;

  GetTodoListUsecase(this._repository);

  Future<List<Todo>> call() => _repository.getTodoList();
}
				
			

Here we import TodoRepository and use it’s object to return value to call method of this use case class. The call() method allows an instance of any class that defines it to emulate a function.

Real project implementation

You can find all of the implementations in the following GitHub link: Github Repository

We are going to create a simple app  that fetches list of todos with api provided by https://jsonplaceholder.typicode.com which will show us the use of remote data source. We will also change theme of the app between light and dark theme which will show us the use of local data source. Here we are going to have two features todo and setting. So let us create respective folders in lib/feature folder as.

  • lib/feature/todo/
  • lib/feature/setting/

Step 1: Creating required entities

We will create todo.dart file in lib/feature/todo/domain/entity directory.

				
					import 'package:equatable/equatable.dart';

class Todo extends Equatable {
  final int userId;
  final int id;
  final String title;
  final bool completed;

  const Todo({
    required this.userId,
    required this.id,
    required this.title,
    required this.completed,
  });
  
  @override
  List<Object?> get props => [userId, id, title, completed];
}
				
			

For setting feature we are going to only store bool value so we will not make any entity.

Step 2: Creating required repositories and functions

We will create todo_repository.dart file in lib/feature/todo/domain/repository directory.

				
					import 'package:flutter_clean_architecture/feature/todo/domain/entity/todo.dart';

abstract class TodoRepository {
  Future<List<Todo>> getTodoList();
}
				
			

Here we use getTodayList() function to fetch list of todos.

Then we create setting_repository.dart in lib/feature/setting/domain/repository directory.

				
					abstract class SettingRepository {
  bool getDarkMode();
  Future<void> setDarkMode(bool darkMode);
}
				
			

Here we use getDarkMode() to get dark mode status and setDarkMode(bool darkMode) to set dark mode on or off.

Step3: Creating required use cases

Each use case represents a single action or functionality.

Create get_todo_list_usecase.dart file in lib/feature/todo/domain/usecase directory.

				
					import 'package:flutter_clean_architecture/feature/todo/domain/entity/todo.dart';
import 'package:flutter_clean_architecture/feature/todo/domain/repository/todo_repository.dart';
import 'package:injectable/injectable.dart';

@LazySingleton()
class GetTodoListUsecase {
  final TodoRepository _repository;

  GetTodoListUsecase(this._repository);

  Future<List<Todo>> call() => _repository.getTodoList();
}
				
			

This use case will be used to get list of todos.

Then we will create get_dark_mode_usecase.dart file in lib/feature/setting/domain/usecase directory.

				
					import 'package:flutter_clean_architecture/feature/setting/domain/repository/setting_respository.dart';
import 'package:injectable/injectable.dart';

@LazySingleton()
class GetDarkModeUsecase {
  final SettingRepository _respository;
  const GetDarkModeUsecase(this._respository);

  bool call() => _respository.getDarkMode();
}
				
			

This use case will be used to get dark mode status for the app.

Then we will create set_dark_mode_usecase.dart file in lib/feature/setting/domain/usecase directory.

				
					import 'package:flutter_clean_architecture/feature/setting/domain/repository/setting_respository.dart';
import 'package:injectable/injectable.dart';

@LazySingleton()
class SetDarkModeUsecase {
  final SettingRepository _respository;
  const SetDarkModeUsecase(this._respository);

  Future<void> call(bool darkMode) async => _respository.setDarkMode(darkMode);
}
				
			

This use case will be used to set dark mode on or off for the app.

This completes our domain layer. In upcoming chapter we will deep dive into other specific implementation.

You can find all of the implementations in following GitHub link: Github Repository

Read more in upcoming chapters to learn more about all of the layers of Clean Architecture.

Flutter Clean Architecture: Introduction & Project Setup
Flutter Clean Architecture: Domain Layer
Flutter Clean Architecture: Data Layer
Flutter Clean Architecture: Presentation Layer
Flutter Clean Architecture: Dependency Injection

Related

Annapurna Circuit Trek

10 Best Treks in Nepal

Flutter Clean Architecture: Dependency Injection

Flutter Clean Architecture: Dependency Injection

Flutter Clean Architecture: Presentation Layer

Flutter Clean Architecture: Presentation Layer

Flutter Clean Architecture: Data Layer

Flutter Clean Architecture: Data Layer

Flutter Clean Architecture Introduction & Project Setup

Flutter Clean Architecture: Introduction & Project Setup