← Back to notes
Flutter & Cross-Platform 2026-06-11 13:39 3 min read Local copy

Flutter Clean Architecture Guide 2026 – Complete Folder Structure

Flutter Clean Architecture Guide 2026 – Complete Folder Structure
BiDev
BiDev

Posted on Jun 11

Flutter Clean Architecture Guide 2026 – Complete Folder Structure
#ai #flutter #cleancode #programming

Flutter Clean Architecture is the industry standard for building scalable, testable, and maintainable mobile apps. This guide walks you through a production-ready implementation for 2026.

**

What Is Clean Architecture?

**
Three concentric layers where dependencies point inward only:

  • *Presentation *— Widgets, pages, state management (Bloc/GetX)
  • *Domain *— Pure Dart: entities, use cases, repository interfaces
  • *Data *— Repository implementations, models, remote/local data sources

**Recommended Folder Structure

**

lib/
├── core/
│   ├── errors/
│   │   ├── exceptions.dart
│   │   └── failures.dart
│   └── usecases/usecase.dart
│
└── features/
    └── auth/
        ├── data/
        │   ├── datasources/auth_remote_datasource.dart
        │   ├── models/user_model.dart
        │   └── repositories/auth_repository_impl.dart
        ├── domain/
        │   ├── entities/user_entity.dart
        │   ├── repositories/auth_repository.dart
        │   └── usecases/login_usecase.dart
        └── presentation/
            ├── bloc/
            │   ├── auth_bloc.dart
            │   ├── auth_event.dart
            │   └── auth_state.dart
            └── pages/login_page.dart

**

Domain Layer — The Core

**

// entity — pure Dart, no packages
class UserEntity {
  final String id;
  final String email;
  final String displayName;
  const UserEntity({required this.id, required this.email, required this.displayName});
}

// repository interface
abstract class AuthRepository {
  Future<Either<Failure, UserEntity>> login({
    required String email,
    required String password,
  });
  Future<Either<Failure, void>> logout();
}

// use case — single responsibility
class LoginUseCase {
  final AuthRepository _repo;
  const LoginUseCase(this._repo);

  Future<Either<Failure, UserEntity>> call(LoginParams params) =>
      _repo.login(email: params.email, password: params.password);
}

**

Data Layer — Repository Implementation

**

class AuthRepositoryImpl implements AuthRepository {
  final AuthRemoteDataSource _remote;
  AuthRepositoryImpl(this._remote);

  @override
  Future<Either<Failure, UserEntity>> login({
    required String email,
    required String password,
  }) async {
    try {
      final model = await _remote.login(email: email, password: password);
      return Right(model);
    } on ServerException catch (e) {
      return Left(ServerFailure(e.message));
    } on NetworkException {
      return Left(const NetworkFailure('No internet connection'));
    }
  }
}

**

Presentation Layer — Bloc

**

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final LoginUseCase _loginUseCase;

  AuthBloc({required LoginUseCase loginUseCase})
      : _loginUseCase = loginUseCase,
        super(AuthInitial()) {
    on<LoginRequested>(_onLoginRequested);
  }

  Future<void> _onLoginRequested(
    LoginRequested event,
    Emitter<AuthState> emit,
  ) async {
    emit(AuthLoading());
    final result = await _loginUseCase(
      LoginParams(email: event.email, password: event.password),
    );
    result.fold(
      (failure) => emit(AuthError(failure.message)),
      (user)    => emit(AuthAuthenticated(user)),
    );
  }
}

**

Dependency Injection with GetIt

**

final sl = GetIt.instance;

Future<void> init() async {
  sl.registerFactory(() => AuthBloc(loginUseCase: sl()));
  sl.registerLazySingleton(() => LoginUseCase(sl()));
  sl.registerLazySingleton<AuthRepository>(
    () => AuthRepositoryImpl(sl()),
  );
  sl.registerLazySingleton<AuthRemoteDataSource>(
    () => AuthRemoteDataSourceImpl(firebaseAuth: sl()),
  );
  sl.registerLazySingleton(() => FirebaseAuth.instance);
}

**

Key Rules

**
Entities have no packages (pure Dart classes only). Models extend entities and add fromJson/toJson. Use cases have one call() method. Blocs live in the presentation layer only. Always use Either to handle errors explicitly.

Originally published on bidev.site

Top comments (0)

Subscribe

For further actions, you may consider blocking this person and/or reporting abuse