Introduction

Of the many important decisions a software development team needs to make when building a complex application, one of the most crucial is going to be determining the overall structure of your project. There are many different architectural patterns one could choose, with an abundance of resources available for each. Out of these patterns, we believe Clean Architecture by Uncle Bob (Robert C. Martin) offers an ideal balance of organization and maintainability with flexibility and quick development. We're going to dive into a quick outline of Clean Architecture here, and our solution to it for Flutter: Clean Framework.

What is Clean Architecture?

Clean Architecture is a unique, robust blueprint for developing software, outlined by Robert C. Martin in his book of the same name. Although it shares some similarities with other software architecture patterns such as Model-View-ViewModel (MVVM) and Model-View-Controller (MVC), there are some key differences that we believe make it the ideal choice for developing Flutter apps.

Why Clean Architecture Stands Out

  1. Separation of Concerns: The core philosophy of Clean Architecture is the separation of software into distinct layers, each with a clearly defined role. This separation makes the system more flexible, maintainable, and testable, and differs from other patterns such as MVC, where there is less separation between the core business logic and UI, for example.

  2. The Dependency Rule: This rule states that dependencies should only point inwards: the innermost layers should not depend on the outer layers. That way, the business logic and application rules will not be affected by changes to external elements like the UI or database systems. As you'll find, this rule is what informs the structure of the rest of the framework, and is the key to avoiding messy, hair-pulling bad dependency cycles.

These are the two core features of Clean Architecture that we believe makes it unique among the available development patterns. There are many similarities between Clean Architecture and other patterns as well as many differences, and we'll cover some of those in another article. We'll also cover the different layers involved with Clean Architecture shortly, and how we went about implementing each in Clean Framework.

Philosophy to Implementation

There are many potential benefits to following a predefined development pattern such as Clean Architecture. However, rendering high-level development philosophies into concrete implementations is not a simple task, and much of it remains up to individual interpretation. Many questions may need to be answered in order to begin writing your app, such as:

  • What will our Use Cases look like?
  • How will we manage dependencies to adhere to the dependency rule?
  • What parts of the architecture do we want to deviate from, and which do we want to adhere closely to?

It can be somewhat time consuming to reach a consensus on what following a pattern such as Clean Architecture might look like for your app. In the case of Clean Architecture, Clean Framework is our answer to this problem in Flutter.

Clean Framework

Clean Framework is a toolkit of classes and implementations that aid developers in creating a layered architecture for Flutter apps, following the principles of Clean Architecture. It provides a structured implementation of the pattern with a focus on maintainability, testability, and separation of concerns, while still preserving flexibility, quick iteration and handling all of the complexities of inter-layer data flow for you. That way, you can spend more time fleshing out the business logic and user experience of your app, and less time on architectural concerns. When developing Clean Framework, we've aimed to utilize the best that Clean Architecture has to offer while simplifying some areas we feel are too complex.

Clean Framework consists of a few different layers, based closely on those laid out by Clean Architecture:

  1. Entity Layer: Data-wise, this is heart of your app. It consists of objects called Entities that contain the core state of each feature. These Entities are immutable, only containing data fields and methods that aid in data retrieval and integration. This layer should reside within the domain directory of each feature.

  2. Use Case Layer: Here's where most of the business logic lives. Use Cases manage the data in Entities and direct data flow. Being Plain Old Dart Objects (PODOs), Use Cases are completely agnostic to the implementation of outside layers, and can interact with various object types via Data Transfer Objects (DTOs) called DomainInputs and DomainModels without fussing over details. This layer should reside within the domain directory of each feature, along with the Entity layer.

    • DomainModels are essentially a snapshot of a part of, or the whole state (Entity) of the feature at a point in time. They transfer data from the Use Case layer to other layers.
    • DomainInputs contain data originating in external services or layers that needs to reach the Use Case.
  3. Adapters Layer: This layer translates DomainInputs and DomainModels into specific messages. It includes two main components:

    • Presenters: The Presenter class translates data from DomainModels originating in the Use Case layer into ViewModels, which contain data and behaviors (such as callbacks) destined for the UI. They handle the UI logic that isn't business-related, such as navigation. Presenters subscribe to DomainModels from Use Cases and update the UI with new View Models. Presenters should reside within the presentation directory of each feature, as their primary role is the management of the UI.
    • Gateways: These handle external data requests to sources such as REST servers, databases, hardware, etc. translating DomainModels into actionable requests. There are two types: a regular Gateway for immediate (synchronous) responses and a WatcherGateway for ongoing subscriptions. These objects should reside in the external_interface directory of each feature, as they are the bridge between the external interface layer and the use case layer.
  4. External Interfaces Layer: This layer is where your features interact with external libraries and dependencies. External Interfaces wait for requests to be received from feature Use Cases, which are then processed depending on request type. Clean Framework offers some ready-to-use sub-packages for GraphQL, REST, and Cloud FireStore:

As hinted at while explaining the different layers, Clean Framework intends for each app to be organized by feature. We'll go over the project structure of a typical Clean Framework app in the next article.

Examples

We provide a few different example projects to showcase the usage and different features of Clean Framework.

example_rest: Pokémon App w/RESTful Backend

Pokemon App

Simple Pokémon browser app, showcasing how a simple app with a clean UI and a RESTful backend (PokéAPI REST API v2) might be written using clean_framework.

example_graphql: Pokémon App w/GraphQL Backend

Simple Pokémon browser app, however instead utilizing a GraphQL backend (PokéAPI GraphQL API v1 beta). This app showcases how the clean_framework_graphql package can be used to easily interact with a GraphQL API.

graphql_example: Scenario Example - Using clean_framework_graphql

Sample app with a bare-bones UI that illustrates the usage of the clean_framework_graphql package in interacting with a GraphQL API.

theme_example: Scenario Example - Updating Theme

Sample app with a bare-bones UI that showcases how one might reach beyond the confines of a Clean Framework feature to update a global app theme, while still adhering to the principles of Clean Architecture.

Conclusion

By emphasizing separation of concerns, dependency inversion, and organizing code around features rather than technical concerns, Clean Architecture facilitates the creation of applications that are easier to test, extend, and maintain. Clean Framework aims to aid developers in adhering to these principles, providing a toolkit that marries theory with practice. Stay tuned to start diving into the process of developing a simple Clean Framework app!