Folder Structure
Overview
Use a feature-first structure with Clean Architecture inside each feature.
This is the default recommendation for modern mobile projects because it:
- Scales better as features and teams grow
- Reduces cross-feature coupling
- Keeps code ownership clear
- Still preserves strict architectural boundaries
Recommended Pattern: Feature-First + Shared Core
At the top level, organize by feature.
Inside each feature, keep presentation, domain, and data.
Keep cross-cutting utilities in core (or shared).
iOS (Swift / SwiftUI)
App/
├── Features/
│ ├── Auth/
│ │ ├── Presentation/
│ │ │ ├── LoginView.swift
│ │ │ └── LoginViewModel.swift
│ │ ├── Domain/
│ │ │ ├── Entities/
│ │ │ │ └── User.swift
│ │ │ ├── UseCases/
│ │ │ │ └── LoginUseCase.swift
│ │ │ └── Repositories/
│ │ │ └── AuthRepository.swift
│ │ └── Data/
│ │ ├── DTOs/
│ │ │ └── TokenDTO.swift
│ │ ├── Mappers/
│ │ │ └── UserMapper.swift
│ │ ├── Repositories/
│ │ │ └── AuthRepositoryImpl.swift
│ │ └── Sources/
│ │ ├── Remote/
│ │ │ └── AuthRemoteDataSource.swift
│ │ └── Local/
│ │ └── KeychainTokenStore.swift
│ ├── Feed/
│ │ ├── Presentation/
│ │ ├── Domain/
│ │ └── Data/
│ └── Profile/
│ ├── Presentation/
│ ├── Domain/
│ └── Data/
└── Core/
├── DI/
│ └── AppContainer.swift
├── Networking/
│ ├── HTTPClient.swift
│ └── AuthInterceptor.swift
├── DesignSystem/
│ ├── PrimaryButton.swift
│ └── AppTextField.swift
├── Logging/
│ └── Logger.swift
└── Extensions/
└── String+Validation.swift
Key Points (iOS)
- Put feature-specific code under
Features/<FeatureName>/... - Keep only shared, reusable infrastructure in
Core - Avoid creating global
DataorDomainfolders when code belongs to a single feature
Android (Kotlin / Compose)
app/
├── features/
│ ├── auth/
│ │ ├── presentation/
│ │ │ ├── LoginScreen.kt
│ │ │ ├── LoginViewModel.kt
│ │ │ └── LoginState.kt
│ │ ├── domain/
│ │ │ ├── entity/
│ │ │ │ └── User.kt
│ │ │ ├── usecase/
│ │ │ │ └── LoginUseCase.kt
│ │ │ └── repository/
│ │ │ └── AuthRepository.kt
│ │ └── data/
│ │ ├── dto/
│ │ ├── mapper/
│ │ ├── repository/
│ │ │ └── AuthRepositoryImpl.kt
│ │ └── source/
│ │ ├── remote/
│ │ │ └── AuthApiService.kt
│ │ └── local/
│ │ └── AuthPreferences.kt
│ ├── feed/
│ │ ├── presentation/
│ │ ├── domain/
│ │ └── data/
│ └── profile/
│ ├── presentation/
│ ├── domain/
│ └── data/
└── core/
├── di/
│ ├── NetworkModule.kt
│ └── DatabaseModule.kt
├── network/
│ └── RetrofitClient.kt
├── designsystem/
│ └── PrimaryButton.kt
├── logging/
│ └── TimberLogger.kt
└── util/
└── Extensions.kt
Key Points (Android)
- Feature packages are the primary unit of ownership
- Keep
coresmall and stable - Prefer one-way dependencies:
presentation -> domain <- data
Flutter (Dart)
lib/
├── features/
│ ├── auth/
│ │ ├── presentation/
│ │ │ ├── login_page.dart
│ │ │ ├── login_bloc.dart
│ │ │ ├── login_event.dart
│ │ │ └── login_state.dart
│ │ ├── domain/
│ │ │ ├── entities/
│ │ │ │ └── user.dart
│ │ │ ├── usecases/
│ │ │ │ └── login_usecase.dart
│ │ │ └── repositories/
│ │ │ └── auth_repository.dart
│ │ └── data/
│ │ ├── models/
│ │ ├── mappers/
│ │ ├── repositories/
│ │ │ └── auth_repository_impl.dart
│ │ └── sources/
│ │ ├── remote/
│ │ │ └── auth_api.dart
│ │ └── local/
│ │ └── token_storage.dart
│ ├── feed/
│ │ ├── presentation/
│ │ ├── domain/
│ │ └── data/
│ └── profile/
│ ├── presentation/
│ ├── domain/
│ └── data/
└── core/
├── di/
│ └── injection.dart
├── network/
│ ├── dio_client.dart
│ └── auth_interceptor.dart
├── design_system/
│ └── primary_button.dart
├── storage/
│ └── storage_config.dart
└── utils/
├── constants.dart
└── extensions.dart
Key Points (Flutter)
- Use
features/<feature>as the default package boundary - Keep feature internals private where possible
- Move only truly shared code into
core
File Naming Conventions
iOS (Swift)
- PascalCase for files and types:
LoginViewModel.swift,AuthRepository.swift - Common suffixes:
View,ViewModel,UseCase,Repository,DTO
Android (Kotlin)
- PascalCase for files and types:
LoginScreen.kt,LoginUseCase.kt - Common suffixes:
Screen,ViewModel,UseCase,Repository,Dto
Flutter (Dart)
- snake_case for files:
login_page.dart,auth_repository.dart - PascalCase for types:
LoginPage,AuthRepository - Common suffixes:
Page,Bloc,Event,State,UseCase,Repository,Dto
Migration Path (Layer-First to Feature-First)
If your project currently uses top-level presentation/domain/data folders, migrate incrementally:
- Create
features/<feature>for one vertical slice (e.g., auth) - Move its
presentation/domain/datafiles together - Keep existing global layers for untouched features
- Repeat feature by feature
- Remove global layer folders once migration completes
This keeps risk low and avoids large refactors in one PR.
Testing Structure
Mirror the source tree by feature:
iOS
AppTests/
├── Features/
│ └── Auth/
│ ├── Presentation/
│ │ └── LoginViewModelTests.swift
│ ├── Domain/
│ │ └── LoginUseCaseTests.swift
│ └── Data/
│ └── AuthRepositoryImplTests.swift
└── Core/
└── Networking/
└── HTTPClientTests.swift
Android
test/
├── features/
│ └── auth/
│ ├── presentation/
│ │ └── LoginViewModelTest.kt
│ ├── domain/
│ │ └── LoginUseCaseTest.kt
│ └── data/
│ └── AuthRepositoryImplTest.kt
└── core/
└── network/
└── RetrofitClientTest.kt
Flutter
test/
├── features/
│ └── auth/
│ ├── presentation/
│ │ └── login_bloc_test.dart
│ ├── domain/
│ │ └── login_usecase_test.dart
│ └── data/
│ └── auth_repository_impl_test.dart
└── core/
└── network/
└── dio_client_test.dart
Summary
For modern mobile apps, prefer:
- Feature-first organization for product code
- Clean Architecture boundaries inside each feature
- A small core/shared module for reusable infrastructure
This structure is easier to scale, easier to assign ownership, and easier to evolve over time.