Flutter Clean Architecture Series — Part 1 (UPDATED)

AbdulMuaz Aqeel
13 min readFeb 18, 2021

--

The Clean Architecture is the most powerful software design which promotes the separation of concerns and the creation of highly maintainable and flexible software systems, and this can be done by organizing the codebase into independent layers that communicate through defined interfaces.

The layers are arranged in a hierarchical manner, with the more abstract and business-specific layers at the top and the more concrete and technical layers at the bottom.

The core idea of Clean Architecture is to define a set of boundaries or layers, with each layer having its own specific responsibilities and dependencies.

Note: These articles have been updated in 2023/02/22 as well as the source code to meet the latest changes occurred in the Flutter framework.

Pros of Clean Architecture

  1. Separation of Concerns: Clean Architecture promotes the separation of concerns, which helps to create a more organized and modular codebase. Each layer has a specific responsibility, and the layers are loosely coupled, which makes the code more maintainable and testable.
  2. Testability: It makes it easier to write automated tests, as the different layers are isolated and can be tested independently. This improves the quality of the software and reduces the risk of introducing bugs.
  3. Flexibility: Clean Architecture promotes a modular design that allows developers to swap out components or change the implementation details without affecting the rest of the system. This makes it easier to adapt to changing requirements or to introduce new features.
  4. Independence from external frameworks: It makes it possible to switch out external frameworks or libraries without affecting the rest of the system. This reduces the risk of being locked into a particular technology or vendor.
  5. Maintainability: It helps to create code that is easier to understand and maintain. By separating concerns and using clear interfaces between components, developers can work on different parts of the system without affecting other parts.

Cons of Clean Architecture

  1. Overhead: It can add some overhead to the development process, as it requires more upfront planning and design. It may also require more code to be written, as each layer needs to have its own set of interfaces and abstractions.
  2. Complexity: It can make the codebase more complex, especially for smaller projects or simpler applications. It may be overkill for small, straightforward applications.
  3. Learning curve: It requires developers to understand the principles and concepts behind the architecture. This can be a challenge for developers who are new to the approach.
  4. Time-consuming: It can take more time to implement than other approaches, especially if the development team is not experienced with the approach.

What this series will explain?

Well, luckily we’re gonna be explaining and building a simple News App that gets its data from REST API (API Key is required) and also caches the data locally in the devices. It’s a lot of work, patience and focusing on how we will implement each part of the app in a very pretty clean way.. and the final results will be awesome.

I’ve separated the entire article into 3 parts, and what this part will explain is what tools/libraries we are gonna be using, the folder structure, different data sources, setup the project, and explaining the main architecture and data layers.

After finishing this series, the app is gonna look like this

Source Code

The complete source code will be available at the end of each part of this series on my GitHub.

Flutter

This project in written using the current latest stable versions of:

Tools/Libraries (Packages)

Packages

  • retrofit: is a source code generator package that uses Dio as an http client to generate the proper methods we need to deal with REST APIs based on abstraction, it’s inspired by the Android Retrofit.
  • floor: is a typesafe, reactive, lightweight source code generator package that uses the sqlite to store its data locally, it’s again inspired by Android Room.
  • flutter_bloc: a flutter state management (You can use any kind of state management you prefer to work with such as provider or riverpod).
  • equatable: a flutter package that makes comparing dart objects by equality is much easier.
  • get_it: a service locator (also a runtime dependency injector).
  • flutter_hooks: hooks inspired by React Hooks.
  • auto_route: a routing package that uses the new Flutter Router instead of the default Navigator.
  • awesome_dio_interceptor: an awesome Dio log interceptor (written by me), which logs network requests and response beautifully.
  • oktoast: a customizable and easy-to-use flutter toast message package.
  • lint: a flutter package that helps you write clean code which follows the Dart’s Style Guidelines, (explained more later).

Another note I would point here is that for the runtime dependency injection, we’ll use the get_it package, we could also use the code generation based package called injectable (inspired by Android Dagger) that uses annotations to automatically generate dependencies at the compile-time, but this would make the project a little bit harder to explain with all those tools and libraries at once, so we will only use the get_it and inject the dependencies manually at the runtime and I’ll explain how to use this injectable in the future articles.

Icons

  • ionicons: a set of beautiful icons available as a flutter package.

Fonts

Your pubspec.yaml file should look like so:

pubspect.yaml

REST API (remote data source)

This article will use this News API for retrieving data as json. You have to sign in to get your own api key.

Folder Structure

Take a look at the diagram below and consider creating folders inside you lib folder as shown in the diagram.

The (data, domain, presentation) folders will be explained later in details, but right now we have to know what the (utils, config) are for?

The config folder includes the configuration about the app (themes, routers, …etc) and anything else related to the app’s configurations.

The utils folder on the other hand, includes (constants, extensions, resources, …etc) and anything related to that.

Also, Create a file called “locator.dart” inside the lib/src folder, this will be responsible for injecting our dependencies using the get_it package.

flutter folder structure diagram

The Architecture and Dependency Rule

If you have created the folders as organized in the diagram, then great job.. because now we will talk more about the architecture that we will use in details and the purpose of each layer.

Firstly, let us take a deep look at this diagram below:

flutter architecture diagram

Take a look again and below is an explanation for each layer.

Data Layer

The Data Layer is one of the layers in the architecture, responsible for managing the storage and retrieval of data used by the application. It interacts with the infrastructure of the application, such as the database, external APIs, or any other data source. It is also responsible for implementing the business logic related to data storage and retrieval. This includes implementing data access methods, handling data validation and mapping, and managing the data source connection and transactions.

One of the main advantages of using a Data Layer in Clean Architecture is that it makes it easier to switch between different data storage mechanisms without affecting the rest of the application. For example, if you need to switch from a SQL database to a NoSQL database, you can simply replace the implementation of the Data Layer without changing any of the other layers in the application.

Keep in mind, that each data source should has it’s own repository implementation.

Presentation Layer

In Clean Architecture, the Presentation Layer is the layer responsible for handling the user interface and user interaction with the application. It is the layer closest to the user and is responsible for presenting data and receiving input.

The Presentation Layer typically consists of user interface components, such as screens, forms, and widgets, and controllers or presenters that handle user input and interaction. It communicates with the Domain Layer through a set of interfaces or protocols that define the operations that can be performed by the user interface.

The benefit of using Presentation Layer is that it makes the codebase more modular and maintainable. By separating the user interface from the business logic and infrastructure, it becomes easier to modify and extend the application over time. Also it allows for different user interfaces to be developed and used with the same underlying business logic. For example, a web application, a mobile application, and a desktop application could all use the same Domain Layer, but have different Presentation Layers that are optimized for their respective platforms.

This layer in our series, contains two important parts: The Bloc (also ViewModels) and The UI

What is a BLoC and Why?

BLoC stands for (Business Logic Components), is a design pattern that is used to manage the state of an application. It separates the business logic from the UI, making it easier to manage and maintain the application’s codebase. The main purpose of BLoC is to provide a clean, reactive and scalable architecture that can handle complex user interfaces and state management.

BLoC pattern is implemented using streams in Dart, which allows for a reactive programming style. In this pattern, the business logic is handled by a bloc, which is a component that receives events from the UI, processes them, and emits new states. The UI, on the other hand, listens to these state changes and updates the UI accordingly.

By using Flutter BLoC, we can write clean, testable and maintainable code, while also benefiting from the advantages of reactive programming and stream-based state management.

Why it’s Loved and Popular by the Developers?

  1. Separation of Concerns: Flutter BLoC allows for the separation of business logic and UI. This means that developers can focus on implementing the business logic in the BLoC without worrying about the UI, and vice versa.
  2. Code Reusability: With Flutter BLoC, developers can create reusable business logic components that can be used across different parts of the application. This reduces code duplication and makes it easier to maintain the application’s codebase.
  3. Predictable State Management: Flutter BLoC provides a predictable way of managing the state of the application. The BLoC emits new states in response to events from the UI, making it easier to reason about the application’s behavior.
  4. Testability: Flutter BLoC makes it easier to write unit tests for the business logic of the application. Since the BLoC is separate from the UI, it can be tested independently of the UI.
  5. Large and Complex Applications: Flutter BLoC is particularly well-suited for large and complex applications, as it provides a scalable architecture that can be extended as the application grows.

Recently, flutter_bloc’s developers have merged something called Cubit along with the Bloc, and basically Cubit is much more easier to use, it has (States and Cubit) but not Events (unlike bloc which has Events, States, Bloc), so instead of adding events in bloc here we just call methods directly from the cubit class.

In this series, we’re gonna be using Cubit instead of Bloc to make things simpler to understand, and it’s totally fine if you choose to work with Bloc since they both are state managements and do the same job but with different mechanism and implementation.

Domain Layer

The Domain Layer is the most important layer of the architecture, as it contains the core business logic of the application. It is responsible for representing the concepts and entities of the business domain, and it defines the rules and behavior of the application. The separation of concerns helps to ensure that the application’s business logic remains independent of the user interface, the data storage mechanism, and other external systems.

In Clean Architecture, the Domain Layer should be independent of any specific implementation or framework. This means that the code in the Domain Layer should be written in a technology-agnostic way, with no dependencies on external systems or libraries.

One of the main benefits of using a Domain Layer in Clean Architecture is that it makes the codebase more modular and maintainable. By separating the business logic from the infrastructure and user interface, it becomes easier to modify and extend the application over time.

Lint

As we said before, lint is a dev dependency package that helps us write the right and clean piece of code that follows specific rules.

After adding the package to the pubspec.yaml file, create (if not already existed) file in the root directory and call it analysis_options.yaml then specify the rules you want to apply and consider following them.

Like the ones here, we’re excluding the analyzer from analyze anything in the generated files (*.g.dart), and also wrote some specific user prefered rules to follow:

analysis_options.yaml

Useful VS Code Extensions

  • bloc: this extension helps you create blocs with all basic codes and files (events, state), as well as code snippets, and other useful bloc functionalities.
  • dart data class generator: a useful extension if you want to quickly generate JSON serialization methods (toJson, fromJson).
  • dart import: a very handy extension that makes sorting and organizing dart imports much easier and cleaner.
  • flutter hooks snippets: a useful code snippets (Written by me) for flutter hooks.

App Configurations

We need to configure our app before we dig deeper into the data layers implementation, and first thing we’re gonna do is to configure our app’s routing:

Routing

Previously, we used to use the default built-in flutter Navigator which does the basic routing stuff, but the Navigator had many issues and flutter team decided to create new routing mechanism, and there Navigator 2.0 (Router) was released, and they’ve also made a package to simplify the usage of it called go_router.

With that being said, We’re gonna use the new flutter routing instead of the old Navigator, but we’re gonna be using a package called auto_route which uses code generation and really makes routing so easy-to-use.

You can absolutely choose whatever the routing package you prefer, and that suits your needs.

Now, after adding the package to your pubspec.yaml file as we talked about it at the beginning of the article, create a folder in this path lib/src/config and call it router, then inside the folder create a file and name it app_router.dart as shown below:

lib/src/config/router/app_router.dart

Currently, we don’t have (BreakingNewsView, ArticleDetailsView, and SavedArticlesView) classes yet, but just to make things clear you can leave the list empty and once we create each view, we’ll come back here and add that route to the list.

This is how we manage our routing, you only have to define your routes in the list and run the build runner to generate the proper code for us in a file called (app_router.gr.dart), then define an instance of that class (appRouter) as global variable below the class to use it anywhere in your app.

One final step left, we need to tell the our MaterialApp that we’re using the new flutter router and provide the necessary methods to work properly with our generated routing code, as shown here:

lib/main.dart

Your main.dart file should look like this one, we’re using MaterialApp.router, then we provide our appRouter’s delegate and the parser to the router.

Note: that our MaterialApp.router is wrapped with a widget called OkToast and this allows us to use the OkToast in order to show a customizable toast message to the user.

Themes

We’re done with the application’s routing, now all we have to do is define our app’s themes and this is pretty much easy.

Create a folder called themes in this path lib/src/config and then inside that folder create a file and name it “app_themes.dart” which will holds your app’s themes, as shown below:

lib/src/config/themes/app_themes.dart

This is our app’s themes class should look like, you can customize it the way you like, but in our case we’re gonna be make it as simple as it is, the only thing left is to add this light theme to your MaterialApp and we’re done.

It’s better to mention that you can use the Material 3 in your theme by setting useMaterial3: true in the ThemeData.

Utils

In the lib/src/utils folder, we’ve constants, extensions, and resources directories which holds our app’s utils, and just to clear things out here.. in both constants and extensions, whenever we want to add const values of a specific type or add functionalities to a specific class, then we separate them in an individual files, you may not really need this complexity but in this example I will follow this approach, so in constants folder we have (nums.dart and strings.dart) each file holds different constants.

And here’s our “strings.dart” file should look like:

lib/src/utils/constants/strings.dart

As well as the extensions, we may need to add an extension method on the ScrollController class, so we create a file under extensions and name it scroll_controller.dart and define our code in that file (will be explained later in Part 2).

Great, now everything should be just fine.. next we will talk in details about how we will get our data (Breaking News) remotely from the REST API.

What’s Next?

Part 2 will explain and implement the remote data (REST API).

Github Source

Consider giving the repo a pretty small star ❤️ for a little support… and see you next.. 😎

Feedback

If you find something wrong or anything else, you can always reach me at

--

--

AbdulMuaz Aqeel
AbdulMuaz Aqeel

Written by AbdulMuaz Aqeel

Senior Software Engineer at Talabatey (I Stand with Palestine 🇵🇸)