Flutter TDD Clean Architecture Course [1] – Explanation & Project Structure

46  comments

Keeping your code clean and tested are the two most important development practices. In Flutter, this is even more true than with other frameworks. On one hand, it's nice to hack a quick app together, on the other hand, larger projects start falling apart when you mix the business logic everywhere. Even state management patterns like BLoC are not sufficient in themselves to allow for easily extendable codebase.

TDD Clean Architecture Course
This post is just one part of a tutorial series. See all of the other parts here and learn to architect your Flutter apps!

The Secret to Maintainable Apps

This is where we can employ clean architecture and test driven development. As proposed by our friendly Uncle Bob, we should all strive to separate code into independent layers and depend on abstractions instead of concrete implementations.

How can such an independence be achieved? Although we're getting ahead of ourselves a bit, on the layered "onion" image below, the horizontal arrows ---> represent dependency flow. For example, Entities do not depend on anything, Use Cases depend only on Entities etc.

All of this is, well, a bit abstract (pun intended). Also, while the essence of clean architecture remains the same for every framework, the devil lies in the details. Principles like SOLID and YAGNI sound nice, you may even understand what they mean, but it won't do you any good if you don't know how to start writing clean code.

The Project We Will Build

As with anything, theory can only take you so far - the real learning comes from doing. We will build an app for getting interesting facts about numbers - a Number Trivia App!

It has all the core components such as getting data from an API, local cache, error handling, input validation and more.  As for the state management, I decided to go with BLoC. You don't have to! I cannot stress this enough.

Clean architecture is not about a particular state management technique. As you will see in a short while, architecting your app properly will make the choice of state management almost inconsequential. I still wouldn't choose the StatefulWidget approach, but anything else is cool.

Clean Architecture & Flutter

To make things clear and Flutter-specific, let me introduce you to Reso Coder's Flutter Clean Architecture Proposal™ to demonstrate something, dare I say, more important than the dependency flow - data & call flow.

Of course, this is only a high-level overview which may or may not tell you much, depending on your previous experience. We will dissect and apply this diagram to our Number Trivia App in a short while.

Explanation & Project Organization

Every "feature" of the app, like getting some interesting trivia about a number, will be divided into 3 layers - presentation, domain and data. The app we're building will have only one feature.

To make creating individual feature folders easier, you can use this VS Code extension.

Presentation

This is the stuff you're used to from "unclean" Flutter architecture. You obviously 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).

Note that the "Presentation Logic Holder" (e.g. Bloc) doesn't do much by itself. It delegates all its work to use cases. At most, the presentation layer handles basic input conversion and validation.

Applied to the Number Trivia App

We will have only a single page with widgets called NumberTriviaPage with a single NumberTriviaBloc.

Domain

Domain is the inner layer which 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 totally independent of every other layer.

Use Cases are classes which encapsulate all the business logic of a particular use case of the app (e.g. GetConcreteNumberTrivia or GetRandomNumberTrivia).

But... How is the domain layer completely independent when it gets data from a Repository, which is from the data layer?  Do you see that fancy colorful gradient for the Repository? That signifies that it belongs to both layers at the same time. We can accomplish this with dependency inversion.

That's just a fancy way of saying that we create an abstract Repository class defining a contract of what the Repository must do - this goes into the domain layer. We then depend on the Repository "contract" defined in domain, knowing that the actual implementation of the Repository in the data layer will fullfill this contract.

Dependency inversion principle is the last of the SOLID principles. It basically states that the boundaries between layers should be handled with interfaces (abstract classes in Dart).

Applied to the Number Trivia App

There won't be much business logic to execute in the app, since we're just displaying interesting number facts. As for the business objects, there will be a single, fairly lean Entity called  NumberTrivia - just a number and the text of the trivia.

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. Repository is where you decide if you return fresh or cached data, when to cache it and so on.

You may notice that data sources don't return Entities but rather Models. The reason behind this is that transforming raw data (e.g JSON) into Dart objects requires some JSON conversion code. We don't want this JSON-specific code inside the domain Entities - what if we decide to switch to XML?

Therefore, we create Model classes which extend Entities and add some specific functionality (toJson, fromJson) or additional fields, like database ID, for example.

Applied to the Number Trivia App

The RemoteDataSource will perform HTTP GET requests on the Numbers APILocalDataSource will simply cache data using the shared_preferences package.

These two data sources will be "combined" in NumberTriviaRepository which will be the single source of truth for the interesting number trivia data.

The caching policy will be very simple. If there is a network connection, always get data from the API and cache it. Then, if there's no network, return the latest cached data.

Next up...

Having the foundational structure of the Number Trivia App's architecture in place, we will begin implementing the inner and most stable layer - domain. We will also add all the packages needed for developing the app, including testing-specific ones like mockito. We're doing test driven development, after all!

Subscribe below to make sure you get notified about the new tutorials and Flutter news.

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a Flutter freelancer and most importantly developer educator, he doesn't have a lot of free time 😅 Yet he still manages to squeeze in tough workouts 💪

You may also like

Flutter Bloc & Cubit Tutorial

Flutter Custom & Staggered Page Transition Animation Tutorial

  • This a very a very good solution for architecting an app. I am thinking to use it for my next project. But what I dont understand is the difference between model / entity. What are u using the model? only form toJson, fromJson. Entity is created based on model. For example Repository get a list of User models (id, job_id), Jobs(id, name) and after it build an UserEntity?
    Thank you

    • Models are data layer dependent – they contain conversion methods (JSON, XML, …) and DB specific fields (ID, …). Entities hold only the fields which make sense from the “business standpoint.
      So yes, Repository will get Models from the data sources and return Entities to the domain layer. However, Entities are NOT based on Models – it’s the other way around. Basically, everything spins around the domain layer when it comes to the clean architecture.

      • The idea sounds good and I’m trying to think about actual implementation.
        For now I’m using built_value for generating classes which represent entities + models speaking your approach.

        From models prospective built_value gives serialization deserialization.

        From entities prospective built_value gives immutability to classes, for example.

        Separating using built_value will be probably problematic.

        Solution might be crating built_value without serialization and extending it with similar class including serialization. Once serialization complete – link to base class should be passed to next layer – domain.

        Am I getting the idea?

  • Hi Reso Coder.
    Do you mind to give me some idea or making a short video about implementation this clean architecture in Firestore CRUD ?

    Thank you so much. Appreciated.

    • I can most definitely make a tutorial on that. Basically though, the principles remain the same as they are in this series. The only real difference would be that you’d have a “FirestoreDataSource” where you would put all of the CRUD operations for a particular feature.

  • Your entities extends equatable for comparing objects, for example in the model unit test. But if the object has attributes that can have setter, IDE provides a warning because equatable is marked as inmutable (and because of the setter they can not be final)
    Any suggestion?
    Thanks, this serie and all post in general are great

  • Loving the tutorials so far! It seems really helpful. I do have a question however about the dio package. If I’m understanding it correctly, it does json serialization for you automatically. Does this mean that I should replace any model classes with dio? Sorry if the question is a bit unclear

  • Greetings, excellent tutorials, I’m learning a lot, I wrote you because I do not see where is the code of the part of Presentation, that in the tutorial 2 you say you already wrote, where can I look at that code? thank you!

  • Hello Reso Coder.

    Thank you so much for all. I follow since a moment and I decide you use your achitecture for my local e-commerce app.

    These my requests:

    1-If for example i have these features (login, registration, manageProfile, ManageShop, etc), all of them must have (domain, data, presentation subfolders ?. If yes, how to share data between them

    2-For datasources, I think to use my api (e.g: http://www.my-site.com/api) with chopper librairy. How can i do it.

    3-If we assume that i use chopper for api call,
    must I have an endpoint for each feature or do I have to put chopper in the core folder?

    Please, i need your help. Thank you so much.

    • Hello!
      The separation into features is something that should help you and it should definitely not cause duplication.

      I usually pull anything which has to be shared between multiple features into the core folder. I found that I usually put most of the data-related classes into core, leaving features with just the presentation layer, basically.

      I’d put Chopper classes for individual endpoints into “feature/data” folder and then pull up only the Chopper set up into the “core” folder.

  • I have one question.
    What if we need some widget like suppose bottom sheet in other features of the app. So when we separate packages for each feature which itself contains the pages and widgets packages then what will happen when some other feature of the app needs to use the same page or widget which is already used by another feature. Should we copy the file or import that package into our new feature?

  • Please Matej Rešetár.

    It’s good to make a custom class to manage the routing between the different screen the app ?

    I think to put inside the core folder.

  • Thank you for your tutorial. currently I refactor my project to this Architecture. After a while I realised, you have many icon image for each folder. what Icon did you use? thank you

  • Hi Reso Coder,

    Could Provider package provide a cleaner architecture as compared to BLoC? I’m new to Flutter, wondering which one is better in terms of learning curve.

  • Hello Reso Coder.
    Thank you for the fantastic series of flutter.
    one thing that I would like to see is a next button at the bottom of the page, for me to easily jump to the next blog post. thanks a lot again~Really appreciate your work

  • Awesome Tutorial!!!
    This is the missing peace I have been looking for. We do not find TDD and Clean in the courses around. Most tutorials and courses just make a basic MVC with classes doing to much work.

  • Hi! Congrats for this articles. You have a clear vision of a clean architecture. Could you suggest a solution for a Stream like repository in this architechture? (like Firebase real-time database). Or you use NetworkInfo in pull way, but how can you use a push like event in this architecture. For example I want to do some buisness logic, when NetworkInfo changed.
    Thanks

  • Hi, thank you for the whole series of tutorial, I have finished it and now I come back here to ask you where should I put the input validation code? For example I have a login feature with user input of email and password and I need to validate the email. Should I put the validation in domain layer? If so, should I put the validation logic in the entity class or create a validator class then inject it into usecase class?

  • This is an awesome tutorial. Thank you very much for the tutorial. By the way, I have a simple question. I have added a feature called “authentication”. That will include 1) User login, 2) Change Password 3)User Register. So I have planned to add multiple blocs under the blocs directory. Also multiple pages under the pages directory. Is this okay? or should I treat each of them as separate features? what is the logic behind that?

  • Hello, really impressed with this architecture. I have small doubt. How to transfer data from screen to next screen in this architecture?

  • Hello, thanks for this fantastic tutorial. I’ve just finished it completely, but I have a question about features.

    I’m developing an application that manages movies. I’ll have basically the list, the creation and the edition.

    Those three things would different features, isn’t it? But as you were talking in some questions before, the model would be placed in the core folder. Is it correct?

    Thanks in advance and congratulations again for your tutorials.

  • If we had to implement an Auth system in the app, where the app checks if the user is already logged in or not, then what would be ‘usecase’ for the ‘Auth’ feature? The app currently has two features i.e. Auth and SignIn

    • No, the repositories in the domain layer are abstract for a reason, they define a contract which the implementations(repositories in data layer) must follow. You’d put things in core, which are shared across features not layers of clean architecture.

  • Thanks for a great tutorial.

    It would be great if there was a short epilogue with the highlights of this tutorial. Something to come back to that refreshes my bad memory. I envision this as a short video with the finished code were you explain the architecture again and how it could be expanded. Also running a debugger and showing the two main workflows.

  • Thanks for the great tutorial!

    I am building a payment app where the user should be able to either scan a QR Code to get the payment information or enter them manually.

    I wonder how you would deal with device specific code like this. I know it should be in the outter most layer, to be easily replaced. But it could be on top of the ui layer or on top of the data layer. Since i think the domain layer should just contain a use case that handles payment with the needed inputs and should not care about how the inputs are get, because this is dependent on the ui design, i think the scan specific code should be put on top of the ui layer.
    So would I then create something like abstract_scanner in the ui layer core and implement it in the device layer?
    How would you handle such a thing?

    How in general can you decide what belongs to the domain layer? (there are a lot of high level clean code resources out there but none of them specify how to know what to put in the domain layer. Do you have any rules of thumb? 😊 )

  • Hi, I’ve been using clean architecture to develop a web app for a while now.
    But i’m not sure if i’m using this the right way,

    First, how does Bloc delegate all its work to use cases?
    I’m using use cases only to get entity implementations from data layer and
    sending them back to server. In my case my Bloc holds entities in one of its states and whenever I make changes to them(change their name or order they are displayed in), I’m building same state with modified entities from previous state and reflecting it in UI.
    should this be handled by use cases instead of Bloc?

    Second, What If two features have identical data and domain layer? Can I put everything from those two layers into core?

    Thanks 🙂

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >