Clean architecture in Flutter14 Mar 2024
A quick guide to implement clean architecture in Flutter.
Why Clean Architecture?
-
Testability: Clean architecture makes it easier to write unit tests and integration tests.
-
Scalability: It makes the codebase more scalable and maintainable.
-
Separation of Concerns: It separates the code into layers of responsibility, making it easier to understand and maintain.
Recommended Packages
- Get it - Dependency injection
- Equatable - Object comparission
- Fpdart - For functional programming
- Flutter Bloc - State management
- Retrofit - For API calls.
Also don't forget to add the retrofit_generator and build_runner in your pubspec.yaml
.
Folder Structure
Click to view image in full screen
Breakdown
-
Data: This layer is responsible for interacting with the data sources(remote or local), such as API calls, local storage, etc.
-
Domain: This layer contains the business logic and the models. Domain layer should be independent of the data layer and the presentation layer. It should contain pure dart code.
-
Presentation: This layer is responsible for the UI and the state management.
Domain
Entity
what type of data our app will be dealing with. It's a simple abstract class for our models. In this case, it can be something like this.
abstract class AuthEntity extends Equatable {
final String? token;
final String? refreshToken;
const AuthEntity({this.token, this.refreshToken});
@override
List<Object?> get props => [token, refreshToken];
}
Use Case
Use cases are basically the business logic of our app. What this feature can do. in our case it'll be log the user in.
class LoginUseCase {
final AuthRepository _authRepository;
LoginUseCase(this._authRepository);
Future<DataState<AuthEntity>> call(String email, String password) {
return _authRepository.login(email, password);
}
}
Repository
This is where we define the contract for our data sources. It's an abstract class that will be implemented by the data layer.
abstract class AuthRepository {
Future<DataState<AuthEntity>> login(String email, String password);
}
Data
Models
This is where we define the models for our app. It's a simple class that extends the entity from the domain layer.
class AuthModel extends AuthEntity {
final String? token;
final String? refreshToken;
const AuthModel({this.token, this.refreshToken});
factory AuthModel.fromJson(Map<String, dynamic> json) {
return AuthModel(
token: json['token'],
refreshToken: json['refreshToken'],
);
}
}
Repository
This is where we implement the repository from the domain layer. It's a class that extends the repository from the domain layer.
class AuthRepositoryImpl implements AuthRepository {
final AuthDataSource _authDataSource;
AuthRepositoryImpl(this._authDataSource);
@override
Future<DataState<AuthEntity>> login(String email, String password) async {
try {
final response = await _authDataSource.login(email, password);
return DataSuccess(AuthModel.fromJson(response.data));
} on DioError catch (e) {
return DataFailed(
DioError(
requestOptions: e.requestOptions,
response: e.response,
type: e.type,
error: e.error,
),
);
}
}
}
Presentation.
Bloc
Now our bloc implementation will be in the presentation layer. It'll talk to use case from the domain layer and will be responsible for managing the state of the UI.
Core
Data State
An abstract class for data state, the repository will be extending this, so we have a common way to handle the response.
import 'package:dio/dio.dart';
abstract class DataState<T> {
final T ? data;
final DioError ? error;
const DataState({this.data, this.error});
}
class DataSuccess<T> extends DataState<T> {
const DataSuccess(T data) : super(data: data);
}
class DataFailed<T> extends DataState<T> {
const DataFailed(DioError error) : super(error: error);
}
Now that we know what each layer does, let's see how they interact with each other.
Click to view image in full screen
Conclusion
This is a simple overview of how to implement clean architecture in Flutter. It's a great way to keep your codebase clean and organized. It's also a great way to make your app more scalable and testable.
References will be added to the Vault soon. `
< Back