In the first part, you learned the core concepts of clean architecture as it pertains to Flutter. We also created a bunch of empty folders for the presentation, domain and data layers inside the Number Trivia App we're building. Now it's time to start filling those empty folders with code, using TDD, of course.
Where to Start?
Whenever you are building an app with a UI, you should design the UI and UX first. I've done this homework for you and the app was showcased in the previous part.
The actual coding process will happen from the inner, most stable layers of the architecture outwards. This means we'll first implement the domain layer starting with the Entity. Before we do that though, we have to add certain package dependencies to pubspec.yaml. I don't want to bother with this file later, so let's fill in everything right now.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
# Service locator
get_it: ^2.0.1
# Bloc for state management
flutter_bloc: ^0.21.0
# Value equality
equatable: ^0.4.0
# Functional programming thingies
dartz: ^0.8.6
# Remote API
connectivity: ^0.4.3+7
http: ^0.12.0+2
# Local cache
shared_preferences: ^0.5.3+4
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^4.1.0
Entity
What kind of data will the Number Trivia App operate with? Well, NumberTrivia entities, of course. To find out which fields this class must have, we have to take a look at the response from the Numbers API. Our app will work with responses from concrete or random number URL, e.g http://numbersapi.com/42?json.
response.json
{
"text": "42 is the answer to the Ultimate Question of Life, the Universe, and Everything.",
"number": 42,
"found": true,
"type": "trivia"
}
We're interested only in the text and number fields. After all, the type will always be "trivia" in our case and the value of found is irrelevant. If a number is not found, we'll get the following response. It's still perfectly fine to display it in the app.
not_found.json
{
"text": "123456 is an unremarkable number.",
"number": 123456,
"found": false,
"type": "trivia"
}
NumberTrivia is one of the few classes which we aren't going to write in a test-driven way and it's for one simple reason - there's nothing to test. It extends Equatable to allow for easy value comparisons without all the boilerplate (which we'd have to test), since Dart supports only referential equality by default.
number_trivia.dart
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
class NumberTrivia extends Equatable {
final String text;
final int number;
NumberTrivia({
@required this.text,
@required this.number,
}) : super([text, number]);
}
Use Cases
Use Cases are where the business logic gets executed. Sure, there won't be much logic in the Number Trivia App - all a UseCase will do is getting data from a Repository. We are going to have two of them - GetConcreteNumberTrivia and GetRandomNumberTrivia.
![domain layer diagram](https://i0.wp.com/resocoder.com/wp-content/uploads/2019/08/domain-layer-diagram.png?resize=141%2C204&ssl=1)
Data Flow & Error Handling
We know that Use Cases will obtain NumberTrivia entities from Repositories and they will pass these entities to the presentation layer. So, the type returned by a UseCase should be a Future<NumberTrivia> to allow for asynchrony, right?
Not so fast! What about errors? Is it the best choice to let exceptions freely propagate, having to remember to catch them somewhere else in the code? I don't think so. Instead, we want to catch exceptions as early as possible (in the Repository) and then return Failure objects from the methods in question.
Alright, let's recap. Repositories and Use Cases will return both NumberTrivia and Failure objects from their methods. How is something like this possible? Please, enter functional programming.
The Either Type
The dartz package, which we've added as a dependency, brings functional programming (FP) to Dart. I won't pretend that I'm some FP pro, at least not yet. You don't need to know a lot of things either. All we're interested in for the purposes of better error handling is the Either<L, R> type.
This type can be used to represent any two types at the same time and it's just perfect for error handling, where L is the Failure and R is the NumberTrivia. This way, the Failures don't have their own special "error flow" like exceptions do. They will get handled as any other data without using try/catch. Let's leave the details of how to work with Either for when we need it in the next parts of this course.
Defining Failures
Before we can proceed with writing the Use Cases, we have to define the Failures first, since they will be one part of the Either return type. Failures will be used across multiple app features and layers, so let's create them in the core folder under a new error subfolder.
![error folder](https://i0.wp.com/resocoder.com/wp-content/uploads/2019/08/error-folder.png?resize=201%2C98&ssl=1)
Failures go into the error folder
There will be one base abstract Failure class from which any concrete failure will be derived, much like it is with regular exceptions and the base Exception class.
failures.dart
import 'package:equatable/equatable.dart';
abstract class Failure extends Equatable {
// If the subclasses have some properties, they'll get passed to this constructor
// so that Equatable can perform value comparison.
Failure([List properties = const <dynamic>[]]) : super(properties);
}
This is enough for now, we will define some concrete Failures, like ServerFailure, in the next parts of this course.
Repository Contract
As you hopefully remember from the last part, and as is siginified on the diagram above, a Repository, from which the UseCase gets its data, belongs both to the domain and data layer. To be more precise, its definition (a.k.a. contract) is in domain, while the implementation is in data.
This allows for a total independence of the domain layer, but there is another benefit which we haven't talked about yet - testability. That's right! Testability and separation of concerns go together extremely well. Oh, the beauty of good architecture...
Writing a contract of the Repository, which in the case of Dart is an abstract class, will allow us to write tests (TDD style) for the UseCases without having an actual Repository implementation.
So, how will the contract look like? It will have two methods - one for getting concrete trivia, another for getting random trivia and the return type of these methods is Future<Either<Failure, NumberTrivia>>, ensuring that error handling will go like a breeze!
number_trivia_repository.dart
import 'package:dartz/dartz.dart';
import '../../../../core/error/failure.dart';
import '../entities/number_trivia.dart';
abstract class NumberTriviaRepository {
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number);
Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia();
}
GetConcreteNumberTrivia
Although this part is getting quite long and information-packed already, I don't want to leave you hanging. We're finally going to write some tests while implementing the GetConcreteNumberTrivia use case. In the next part, we will add the GetRandomNumberTrivia use case, so definitely stay tuned for that!
As is the case with TDD, we are going to write the test before writing the production code. This ensures that we won't add a bunch of things that we "ain't gonna need" and we'll also get the confidence that our code isn't going to fall apart like dominoes.
Writing the Test
In Dart apps, tests go into the test folder and it's a custom to make the test folders map the lib folders. Let's create all the root ones and also a folder called "usecases" under "domain".
![test folder structure](https://i0.wp.com/resocoder.com/wp-content/uploads/2019/08/test-folder-structure.png?resize=252%2C255&ssl=1)
The test we're about to write goes into the usecases folder
Create a new file under the "usecases" test folder called get_concrete_number_trivia_test.dart and while we're at it, also a get_concrete_number_trivia.dart under "usecases" lib folder.
Let's set up the test first. We know that the Use Case should get its data from the NumberTriviaRepository. We'll mock it, since we only have an abstract class for it and also because mocking allows us to check, among other things, if a method has been called.
get_concrete_number_trivia_test.dart
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/repositories/number_trivia_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRepository {}
To operate with this NumberTriviaRepository instance, the GetConcreteNumberTrivia use case will get it passed in through a constructor. Tests in Dart have a handy method called setUp which runs before every individual test. This is where we will instantiate the objects.
NOTE that the code we're writing will be full of errors - we don't even have a GetConcreteNumberTrivia class yet.
get_concrete_number_trivia_test.dart
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/repositories/number_trivia_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRepository {}
void main() {
GetConcreteNumberTrivia usecase;
MockNumberTriviaRepository mockNumberTriviaRepository;
setUp(() {
mockNumberTriviaRepository = MockNumberTriviaRepository();
usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
});
}
Although we haven't really written any tests yet, now it's a good time to start writing the production code. We want to make a skeleton for the GetConcreteNumberTrivia class, so that the setup code above will be error-free.
get_concrete_number_trivia.dart
import '../repositories/number_trivia_repository.dart';
class GetConcreteNumberTrivia {
final NumberTriviaRepository repository;
GetConcreteNumberTrivia(this.repository);
}
Now comes the time to write the actual test. Since the nature of our Number Trivia App is simple, there won't be much logic in the Use Case, actually, no real logic at all. It will just get data from the Repository.
Therefore, the first and only test will ensure that the Repository is actually called and that the data simply passes unchanged throught the Use Case.
get_concrete_number_trivia_test.dart
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/repositories/number_trivia_repository.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/usecases/get_concrete_number_trivia.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRepository {}
void main() {
GetConcreteNumberTrivia usecase;
MockNumberTriviaRepository mockNumberTriviaRepository;
setUp(() {
mockNumberTriviaRepository = MockNumberTriviaRepository();
usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
});
final tNumber = 1;
final tNumberTrivia = NumberTrivia(number: 1, text: 'test');
test(
'should get trivia for the number from the repository',
() async {
// "On the fly" implementation of the Repository using the Mockito package.
// When getConcreteNumberTrivia is called with any argument, always answer with
// the Right "side" of Either containing a test NumberTrivia object.
when(mockNumberTriviaRepository.getConcreteNumberTrivia(any))
.thenAnswer((_) async => Right(tNumberTrivia));
// The "act" phase of the test. Call the not-yet-existent method.
final result = await usecase.execute(number: tNumber);
// UseCase should simply return whatever was returned from the Repository
expect(result, Right(tNumberTrivia));
// Verify that the method has been called on the Repository
verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
// Only the above method should be called and nothing more.
verifyNoMoreInteractions(mockNumberTriviaRepository);
},
);
}
When you think about it, the test above reads like documentation, even without all of my comments. Running the test now is pointless, since there are even compilation errors, so we can jump into implementation.
All we need to add to the GetConcreteNumberTrivia use case is the following function which will do everything prescribed by the test.
get_concrete_number_trivia.dart
import 'package:dartz/dartz.dart';
import 'package:meta/meta.dart';
import '../../../../core/error/failure.dart';
import '../entities/number_trivia.dart';
import '../repositories/number_trivia_repository.dart';
class GetConcreteNumberTrivia {
final NumberTriviaRepository repository;
GetConcreteNumberTrivia(this.repository);
Future<Either<Failure, NumberTrivia>> execute({
@required int number,
}) async {
return await repository.getConcreteNumberTrivia(number);
}
}
When you now run the test (if you don't know how, refer to your IDE documentation), it's going to pass! And with that we've just written the first Use Case of the Number Trivia App using TDD.
In the next part, we're going to refactor the above code, create a UseCase base class to make the app easily extendable and add a GetRandomNumberTrivia use case. Subscribe to the mailing list below to get notified about new tutorials and much more from the world of Flutter!
Excellent understanding Matej.. Just one concern with regards to using dartz package vs RxDart. Can RxDart be used instead of Dartz or is there any particular reason we are going with Dartz.
We are going to be using RxDart indirectly through the flutter_bloc package. Dartz is here only for the Either type so that we can have a clean “error flow” without catching exceptions everywhere.
Thanks for this series!
I’m curious about why it’s so bad to stick with exceptions in all layers.
The domain layer would have to receive generalized exceptions that aren’t directly thrown by operations done by the data layer (they’d need to be caught and have a different exception thrown, defined in the domain layer).
Other than that, why not use try-catch blocks instead of functional patterns?
Hi. thanks for this very informative series and what you are doing for the community.
I think the equatable package has changed and now the properties are taken via a getter instead of through the constructor. I’m using Equatable 0.6.1
Thank you and yes, that’s correct!
the getter in the Failure-class is causing my test to fail… it says expected Right<dynamic, … but actual Right<Failure, …
What can I do to fix this?
changing Right(tNumberTrivia)); to Right(tNumberTrivia); fixed it for me!
Aimane Najja, what you just posted makes very little sense. Can you post the full snippet for proper context?
Hi @Matej,
dart-import Extension showing this error…
And googling can’t fix my problem.
Current file is not on project root or not on lib folder? File must be on $root/lib. Your current file path is: ‘d:Flutternumber_exclibfeaturesnumber_triviadomainrepositoriesnumber_trivia_repository.dart’ and the lib folder according to the pubspec.yaml file is ‘d:Flutternumber_exc/lib’.
Are you trying to format imports in a test file? That’s not going to work.
If you try to fix the import in the test folder, it gives an error. So first go to the lib folder and then try to fix the import.
Thanks a lot Matej for taking your time to teach others. This series are great stuff.
Thanks for teaching us.
If possible please provide buttons to move to the previous and next article at the end of the page.
Why doesn’t the entity have the id? For example: Suppose the data source is a sqflite database, how could I get a user’s data by id if the entity doesn’t take the user id into the widget?
Great work! I love your tutorials! Could you explain how you would implement additional features without code duplication and how you handle communication for shared data?
Hi Matej!
First of all, thank you so much for such a great job! The tutorial is amazing and project results in a really clean and organized architecture.
I’m implementing this with a simple UseCase of a SignIn. In my case, I’ve got a UserRepository with the following method (User is the entity):
Future<Either> signIn(String email, String password);
In the other hand my UseCase has its method execute returning the same: Future<Either>.
However, when running my test the very first time, I’m getting error:
Expected: Right:
Actual: <Instance of 'Future<Either>’>
Why is that? My code on the test is exactly the same as yours, but instead of a tNumber, I have a tEmail and tPassword. Could you please shed some light on this?
Thank you so much!
Ok. For some reason the blog comments are getting parts of the comments as HTML tags I guess.
The type of the Future is Either Failure or User.
The error I’m getting is:
Expected: Right[dynamic, User]:[Right(User)]
Actual: [Instance of ‘Future[Either[Failure, User]]’]
(I’ve replaced “lower than” and “greater than” with box brackets)
Forget it… I’m an idiot.. I forgot await keyword before usecase.execute(), that’s why I was getting a Future instead of the actual Right(User)
Thank you so much Matej for this serie I used to get in touch with Flutter!
Now a few months after than you publication, Equatable is now at version 1.0.2, and it seems that the syntax used for the super() in the Failure abstract class is no longer supported.
I’ve been struggling to fix that issue, but I’m not so experienced with Dart nor Equatable, and I couldn’t manage to adapt the code there…
Any idea maybe ?
you need to remove the super part as we don’t rely on the superclass constructor and you have to implement props so the new class will be something like this:
/////////////////////////////////////////////////////////////////////////////////////////
import ‘package:equatable/equatable.dart’;
import ‘package:meta/meta.dart’;
class NumberTrivia extends Equatable {
final String text;
final int number;
NumberTrivia({
@required this.text,
@required this.number,
});
@override
List get props => [text, number];
}
With null safety, use the ‘required’ keyword, not the ‘@required’ annotation
Really great stuff, thanks for your great work, extremely useful indeed.
Hi,
First of all, excellent series! Just loved it. Excited for your DDD series.
My doubt is, that when I run these tests on Docker, the docker is unable to find the fixture files. Any idea why?
(Although it runs the tests even though they are also right under the test directory.)
Uh, no idea why that happens ?
I have the same issue using json-files with Travis Ci and Github Actions. So I had to do a new class that returns objects of it instead. Works good but would rather find a better solution. Sound like it might be the same issue, have you found any solution?
Dhruvam please how to test on Docker, I need it now on my project so I have’nt make it. (I’m French so apologize for my english)
Hi Matt,
I’m new to TDD
Why did you overide the getConcreteNumberTrivia functionality, using when(..).thenAnswer() ?
this will return the same result even when the implementation of the mock is not complete or return unexpected results, this always gives green indicator.
I know I’m wrong, but unable to see how?
thank
No worries, progressing in the series, I’m now more familiar with how mocking works ^_^
Which extension provides these colourful folder icons?
Hey Matt and thanks a lot for your great tutorials! I ran into an issue when executing the test. VSCode’s auto-completion turned the ‘void main()’ to a ‘void main(List args)’ . I thought that this shouldn’t be a problem since we aren’t using the args in the code. But the test wouldn’t succeed this way. Do you know why?
Hi, I learned that one of the main rule in clean archi is there no third-package import in the business logic and entities, here you use equatable for example.
is it the exception that confirms the rule ? 🙂
Thanks for this awesome tutorial.
I’ve a question, as we are defining NumberTriviaRepository (domain > repository) as abstract class, how are we able to instantiate it in GetConcreteNumberTrivia (domain > usecase). As far as I know abstract class cannot be instantiated. Then how is it working?
thought the same
I get error in my abstract Failure class, i’m using equatable 1.2.5, and must use get props. Any help
Thanks for the tutorial! How do you suggest handling shared entities across features (e.g. User)? Should that be pulled into core/domain/entities?
Hi!, first of all I love this series, I’m using this for a while now but I have one mayor issue. When you have a usecase that calls multiple repositories, get data from them and execute some business logic, since the Failure is not an Exception and is not thrown by the repositories, but is returned by them, you whould have to add if statements for each repository call inside your use case, checking whether that call returned the correct data or a Failure, isn’t that right?. In the number trivia app this is not evident because all usecases do only one thing, retrieve data from one repository and do not process any data (which is fine since is front end and a simple app), but if you apply this on a backend for example, and create this Failure class, your would have the problem I discribed. I’m asking for clarification on this if anyone can help.
By the way, Reso Coder did another video where he explains how to handle errors, where he creates a Failure class too, but this time, he throws Failure objects so he can catch them later.
In get_concrete_number_trivia_test.dart file I cannot use this line of code. It always shows an error.
test(
‘should get trivia for the number from the repository’,
() async {
//arrange
when( mockNumberTriviaRepository.getConcreteNumberTrivia(any)).thenAnswer((_) async {
return Right(tNumberTrivia);
});
I cannot use any keyword here. It always shows an error: “The argument type ‘Null*’ can’t be assigned to the parameter type ‘int'”.
I correctly added mockito package also. (mockito: ^5.0.14)
That’s because of null-safety: https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md
There are several steps to fix it:
1. in pubspec.yaml in dev_dependencies section add:
build_runner: ^2.1.1
2. in get_concrete_number_trivia_test.dart add:
import ‘package:mockito/annotations.dart’;
and just before the main function add the annotation:
@GenerateMocks([MockNumberTriviaRepository])
3. then run:
flutter pub run build_runner build
to generate the new mock class based on MockNumberTriviaRepository.
By default a generated class name will be MockMockNumberTriviaRepository and its file name will be get_concrete_number_trivia_test.mocks.dart
4. so add import:
import ‘get_concrete_number_trivia_test.mocks.dart’;
and in main function change type MockNumberTriviaRepository to MockMockNumberTriviaRepository
Perfect answer!
Unfortunately, it’s still not working for me, only difference which I had was instead of
“build_runner: ^2.1.1” I am having
build_runner:
because I was having some issue that specific version and http package.
Excellent answer. I want to make a contribution. You can directly use
@GenerateMocks([],
customMocks: [MockSpec(as: #MockHomeRepository)])
and delete mock class
If anyone has an error like “type ‘Null’ is not a subtype of type ‘Future<Either>” then Migrate to Mocktail.
Because Mocktail supports stubbing/verifying non-nullable return types.
I was trying to solve it for 2 hours, thanks for your comment
Thank you, great article! I have a question that I hope you can help me with. How to handle all api calls for a particular error, for example I need to handle 501 error from the server when the interface needs to switch to the login screen.
const Failure([List properties = const []]) : super(properties);
Too many positional arguments: 0 expected, but 1 found.
Try removing the extra arguments.
Remove the super, on this case: Failure([List properties = const []]);
when(mockNumberTriviaRepository.getConcreteNumberTrivia(any))
The non-nullable local variable ‘mockNumberTriviaRepository’ must be assigned before it can be used.
Try giving it an initializer expression, or ensure that it’s assigned on every execution path
(any) – The argument type ‘Null’ can’t be assigned to the parameter type ‘int’.
You can use late MockNumberTriviaRepository mockNumberTriviaRepository;
This looks like a REALLY good course. Thank you for it. However, as of 12/2/2021, I’m running into a number of problems relating to out-of-date packages (including BLoC which is at 8.0) and mockito and a deprecated version of the Android embedding. SInce I’m new to Flutter, these are major stumbling blocks for me. Any chance you’ll be upgrading this to the current versions?
Seems to be a number of issues with this section as others have alluded to with the the get_concrete_number_trivia_test_.dart class and Mockito dependencies.
CODE IN 2022
import ‘package:clean_architecture_tdd_course/features/number_trivia/domain/entities/number_trivia.dart’;
import ‘package:clean_architecture_tdd_course/features/number_trivia/domain/repositories/number_trivia_repository.dart’;
import ‘package:clean_architecture_tdd_course/features/number_trivia/domain/usecases/get_concrete_number_trivia.dart’;
import ‘package:dartz/dartz.dart’;
import ‘package:flutter_test/flutter_test.dart’;
import ‘package:mockito/annotations.dart’;
import ‘package:mockito/mockito.dart’;
import ‘get_concrete_number_trivia_test.mocks.dart’;
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRepository {}
@GenerateMocks([MockNumberTriviaRepository])
void main() {
late final GetConcreteNumberTrivia usecase;
late final MockMockNumberTriviaRepository mockNumberTriviaRepository;
setUp(() {
mockNumberTriviaRepository = MockMockNumberTriviaRepository();
usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
});
const tNumber = 1;
final tNumberTrivia = NumberTrivia(text: ‘Test’, number: 1);
group(‘Successful tests:’, () {
test(‘Should get trivia for the number from the repository’, () async {
// Arrange
when(mockNumberTriviaRepository.getConcreteNumberTriviaRepository(any))
.thenAnswer((_) async => Right(tNumberTrivia));
// Act
final result = await usecase(Params(number: tNumber));
// Assert
verify(mockNumberTriviaRepository
.getConcreteNumberTriviaRepository(tNumber));
verifyNoMoreInteractions(mockNumberTriviaRepository);
expect(result, Right(tNumberTrivia));
});
});
}
Observations:
1. In order to mock our repository we need to run the following command: flutter pub run build_runner build
*Run this command only after writing @GenerateMocks([MockNumberTriviaRepository]) upon the void main function.
2. After mocking our repository, the error at the parameter any, in the method mockNumberTriviaRepository.getConcreteNumberTriviaRepository(any), will stop being shown.
I did everything as presented here and in other comment however, the problem persists. Any is still not accepted as parameter because Null can’t be assigned to int
Thank you for your video which contains clear explanations
I still have a question about the entities, since with this architecture, each feature contains three main folders representing the 3 layers, domain, data and data source.
What if an entity is used in two different features? Do we write it twice?
Hi,
Doing this on March 2023, find some problems about mockito and the null safety actualization in the get_concrete_number_trivia_test.dart test.
I solved this using mocktail package and get the following code:
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRepository {}
void main() {
late GetConcreteNumberTrivia usecase;
late MockNumberTriviaRepository mockNumberTriviaRepository;
setUp(() {
mockNumberTriviaRepository = MockNumberTriviaRepository();
usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
});
final tNumber = 1;
final tNumberTrivia = NumberTrivia(text: ‘test text’, number: 1);
test(
‘should get trivia for the number from the repository’,
() async {
when(() => mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber))
.thenAnswer((_) async => Right(tNumberTrivia));
final result = await usecase.execute(number: tNumber);
expect(result, Right(tNumberTrivia));
verify(() => mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
verifyNoMoreInteractions(mockNumberTriviaRepository);
},
);
}
what a life saver, if you’re in 2023 make sure to follow this!
Thanks, Anag, saved me hours of debugging.
I’ve been spending hours to solve this. Thanks a lot
A great job that has helped me understand more than 3 years of study.
I have a question about entities and models. The entity must closely represent the raw data received from the API, without any transformation logic. By adding the == and hashCode methods using the Equatable plug-in, aren’t we violating this principle?
What do you think if we leave the Entity class simple and use the freezed pluglin in the Model class? The freezed plugin not only replaces the == and hashCode methods but also replaces fromMap() and toMap()? https://pub.dev/packages/freezed
By doing it this way, the Separation of Responsibilities principle is preserved and the entities are easy to reuse. Isn’t that the vision of clean architecture?
Your point of view caught my eye and was very interesting. Thanks. I have a question for you.
Love your tutorials mate! Thanks for doing that!
There’s something that’s bugging me a bit though and would like to know your thoughts. As far as I understand, use cases are written in Dart and should be independent of any frameworks or libraries. Although dartz builds on top of the Dart language it still is a package that the use case depends on. Shouldn’t we return custom “response models” and inside the use cases and let the interface adaptors use the dartz to return an either? That way we’d be fine if at one point we decided to swap dartz to another package?
Hello! Im trying to reply it to my vscode but the test isnt working due to the following error:
type ‘Null’ is not a subtype of type ‘Future<Either>’
testfeaturesnumber_triviadomainusecasesget_concrete_number_trivia_test.dart 8:7 MockNumberTriviaRepository.getConcreteNumberTrivia
testfeaturesnumber_triviadomainusecasesget_concrete_number_trivia_test.dart 39:39 main.
in the test command, i tried to use the ‘any’ but the code wasnt compiling, so i had to change it to tNumber, but then this error began to happen. Below is my code:
import ‘package:app/features/number_trivia/domain/entities/number_trivia.dart’;
import ‘package:app/features/number_trivia/domain/usecases/get_concrete_number_trivia.dart’;
import ‘package:dartz/dartz.dart’;
import ‘package:mockito/mockito.dart’;
import ‘package:flutter_test/flutter_test.dart’;
import ‘package:app/features/number_trivia/domain/repositories/number_trivia_repository.dart’;
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRepository {}
void main() {
late GetConcreteNumberTrivia usecase;
late MockNumberTriviaRepository mockNumberTriviaRepository;
setUp((){
mockNumberTriviaRepository = MockNumberTriviaRepository();
usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
});
const tNumber = 1;
const tNumberTrivia = NumberTrivia(number: tNumber, text: ‘test’);
test(
‘should get trivia for the number from the repository’,
() async {
// arrange
when(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber)).thenAnswer((_) async => const Right(tNumberTrivia));
// act
final result = await usecase.execute(number: tNumber);
// assert
expect(result, const Right(tNumberTrivia));
verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
verifyNoMoreInteractions(mockNumberTriviaRepository);
},
);
}
Thanks for your attention and for the great content!!
Leonardo