(β)Log

Flutter Project Structure: Layer First or Features First 🤔?

Published on
Authors

Background

The main thing before we start working on Flutter App Development is to decide the Project Structure. There are things to keep in mind to ensure a scalable and manageable project. In the Architecture Partitioning, we can separate between Technical Partitioning (Layer First) structure and Domain Partitioning (Features First) structure. So we can choose which one fit with our team and requirements.

Layer First (features inside layers) 🤌

In the Technical Partitioning (Layer First), we separate the application components by technical. For example, the common thing we will find is Layered (N-Tier) Architecture. Which is grouped by technical, for example:

 lib
	└─  data
	|   └─ datasources
	|   |  └─ local
	|   |  └─ remote
	|   └─ models
	|   └─ repositories
	└─  domain
	|   └─ entities
	|   └─ repositories
	|   └─ usecases
	└─  pages
	     └─ feature
		    └─ cubit

Explanation:

  • data: The data layer consists of a Repository implementation (the contract comes from the domain layer) and data sources - one is usually for getting remote (API) data and the other for caching that data. The repository is where you decide if you return fresh or cached data, when to cache it and so on.
    • datasources: Based on the structure, we can see mostly just have 2 data resources, from local or remote.
      • local: Local data source /database, for example, Hive or SharedPreferences.
      • remote: Remote data sources like Firebase Realtime Database or Rest API.
        • model: This model is from Response API.
        • services: Configuration for network clients, like dio or Firestore.
    • models: The place for models response from API.
    • repositories: Here we implement contracts repositories from the domain.
  • domain: is the inner layer that shouldn't be susceptible to the whims of changing data sources or porting our app to Angular Dart. It will contain only the core business logic (use cases) and business objects (entities). It should be independent of every other layer.
    • entities: This data should be visible to the user, so we do transform raw JSON from API to the entity.
    • repositories: The place where we define all contracts will then be implemented on the data layer.
    • usecases: Implement usecase from the core, here we define business logic from the Application.
  • pages: This is the stuff you're used to from "unclean" Flutter architecture. You need widgets to display something on the screen. These widgets then dispatch events to the Bloc and listen for states (or an equivalent if you don't use Bloc for state management).
    • screen name, ex: login: All related to UI and User Interaction on the User screen.
      • cubit: Here we handle state management and let UI know when the state is changed from onLoading, onSuccess, and onError.

Features First (layer inside features) 🫱🏻‍🫲🏼

Different with Technical Partitioning (Layer First), Domain Partition (Features First) separate the application components based on Feature / Domain in business, which is all technical partitioning grouped by Feature/ Domain.

And how we handle for the shared code like main or home screen? In these case, we can create new feature called common, shared or general. But what if you have more than 20 features, and some code needs to be shared by only two of them, should we put that on shared folder? In this scenario, I will import the feature to which one feature need it. For example, I have Feature A but in the Feature C I need to use models or repository from Feature A, so I will just import the Feature A to the Feature C. But there is no right or wrong answer, and you have to use your best judgement on a case-by-case basis or you can discuss with your team.

Here example your project structure with features first:

 lib
	└─ features
		  └─ auth
		  |  └─ data
		  |  └─ domain
		  |	 └─ pages
		  └─ user
		  └─ transaction
		  └─ payment

When I should use it

Based on my experience, Layer First and Features First have pros and cons. If your application is simple and the member of your team less than 5, layer first is fit for your team because it is easy to maintain, you just need to define the layer and is easier to discuss with your team.

But when your application is complex, have a lot of feature, and a lot of developers, features first will be the best option. Because every developer will be responsible based on what feature they're working on. Fewer dependencies and reduce conflict on your project repository because they're only working on their features. And we can also just delete the features, instead need to check features on every layer if we're still using layer first project architecture and remove it one by one.

Bonus 🫰🏻

Here my common project structure I use for now.

Project structure
lib/
├── core
│   ├── api
│   ├── error
│   ├── localization
│   │   └── generated
│   ├── resources
│   ├── usecase
│   └── widgets
├── features
│   ├── auth
│   │   ├── data
│   │   │   ├── datasources
│   │   │   ├── models
│   │   │   └── repositories
│   │   ├── domain
│   │   │   ├── entities
│   │   │   ├── repositories
│   │   │   └── usecases
│   │   └── pages
│   │       ├── login
│   │       │   └── cubit
│   │       └── register
│   │           └── cubit
│   ├── general
│   │   └── pages
│   │       ├── main
│   │       ├── settings
│   │       │   └── cubit
│   │       └── splashscreen
│   └── users
│       ├── data
│       │   ├── datasources
│       │   ├── models
│       │   └── repositories
│       ├── domain
│       │   ├── entities
│       │   ├── repositories
│       │   └── usecases
│       └── pages
│           └── dashboard
│               └── cubit
└── utils
    ├── ext
    ├── helper
    └── services

Test Folder
test/
├── features
│   ├── auth
│   │   ├── data
│   │   │   ├── datasources
│   │   │   │   ├── models
│   │   │   │   └── repositories
│   │   │   └── repositories
│   │   ├── domain
│   │   │   └── usecases
│   │   └── presentation
│   │   ├── login
│   │   │   └── cubit
│   │   └── register
│   │   └── cubit
│   ├── general
│   │   └── presentation
│   │   └── settings
│   │   └── cubit
│   └── users
│   ├── data
│   │   ├── datasources
│   │   │   ├── models
│   │   │   └── repositories
│   │   └── repositories
│   ├── domain
│   │   └── usecases
│   └── presentation
│   └── dashboard
│   └── cubit
└── helpers
└── data_dummy

also you can check it on my GitHub