While the domain layer is the safe center of an app which is independent of other layers, the data layer is a place where the app meets with the harsh outside world of APIs and 3rd party libraries. It consists of low-level Data Sources, Repositories which are the single source of truth for the data, and finally Models.
Going Outwards
You may have noticed that we always start working from the inner parts of the app and work our way to the outskirts. After all, we started with the completely independent domain and even there we first created the Entity. Now, we'll start with the Model and only then go ahead and implement the Repository and the low-level Data Sources. Why?
The entire Clean Architecture is predicated on one basic principle - outer layers depend on inner layers, as signified by those vertical arrows ---> at the famous picture below.

Dependencies flow inward
Therefore, it makes sense to start at the center, since otherwise we'd have a hard time implementing the Use Cases, for example, if we didn't have any Entities which can be returned first.
All of this is possible only through the power of contracts, which in the case of Dart are implemented as abstract classes. Think about it, we've fully implemented the domain layer using TDD and yet, all that the Use Cases depend on is just an abstract NumberTriviaRepository class which we've mocked with fake implementation all along.
Data Layer Overview
We know how the repository implementation's public-facing interface is going to look like (it's defined by the following contract from the domain layer).
number_trivia_repository.dart
abstract class NumberTriviaRepository {
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number);
Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia();
}
Of course, this data cannot be pulled out of thin air, so before creating the Repository implementation, we will have to make the remote and local Data Sources first.
Again, we won't need to create their implementations straight away. It will be enough to make their contracts, which they must fulfill, using abstract classes. We will then mock these data sources while writing tests for the repository in the later parts of this course. But first...

Data layer operates with Models
The Need for Models
Method return types of the data sources will be very similar to the ones in the repository but with two HUGE differences. They're not going to return the errors "inline" by using Failures, instead they will throw exceptions. Also, rather than returning NumberTrivia entities, they're going to return NumberTriviaModel objects.
All of this happens because data sources are at the absolute boundary between the nice and cozy world of our own code and the scary outside world of APIs and 3rd party libraries.
Models are entities with some additional functionality added on top. In our case, that will be the ability to be serialized and deserialized to/from JSON. The API will respond with data in a JSON format, so we need to have a way to convert it to Dart objects.
You may have noticed that the NumberTriviaModel will contain some JSON conversion logic. This word should start flaring red lights in your head because it means employing test-driven development again.
Implementing Model with TDD
The file for the model itself will live under the models folder for the number_trivia feature. As always, its accompanying test will be at the same location, only relative to the test folder.

The "production" model file
Since the relation between the Model and the Entity is very important, we will test it to be able to have a good night's sleep.
number_trivia_model_test.dart
import 'package:clean_architecture_tdd_prep/features/number_trivia/data/models/number_trivia_model.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
final tNumberTriviaModel = NumberTriviaModel(number: 1, text: 'Test Text');
test(
'should be a subclass of NumberTrivia entity',
() async {
// assert
expect(tNumberTriviaModel, isA<NumberTrivia>());
},
);
}
To make the above test compile and pass, the NumberTriviaModel will extend NumberTrivia and simply pass all the constructor parameters to the super class.
number_trivia_model.dart
import 'package:meta/meta.dart';
import '../../domain/entities/number_trivia.dart';
class NumberTriviaModel extends NumberTrivia {
NumberTriviaModel({
@required String text,
@required int number,
}) : super(
text: text,
number: number,
);
}
Let's first conversion logic we'll implement will be the fromJson method which should return a NumberTriviaModel instance with the same data as is present inside the JSON string.
We aren't going to get the JSON string from the "live" Numbers API. Instead, we will create a fixture which is just a regular JSON file used for testing. That's because we want to have a predictable JSON string to test with - for example, what if the Numbers API is under maintenance? We don't want any outside forces to mess with the results of our tests.
Creating Fixtures
The content of the fixture will mimic the JSON response from the API. Let's see how the response from the random endpoint (http://numbersapi.com/random/trivia?json) looks like.
response.json
{
"text": "418 is the error code for \"I'm a teapot\" in the Hyper Text Coffee Pot Control Protocol.",
"number": 418,
"found": true,
"type": "trivia"
}
We have to make sure to know about all the different edge cases for a response. For example, the number doesn't always have to be a nice integer. Sometimes, it can be something which Dart would regard as a double, even though it's really just an integer.
response.json
{
"text": "4e+185 is the number of planck volumes in the observable universe.",
"number": 4e+185,
"found": true,
"type": "trivia"
}
We will now take these responses and "freeze them in-place" by creating two fixtures - trivia.json and trivia_double.json. They will all go into a folder called fixtures which is nested right inside the test folder.

Fixtures inside a folder
While these JSON fixture files have to have the same fields as the actual responses, we'll make their values simpler to make it easier for us to write tests with them.
trivia.json
{
"text": "Test Text",
"number": 1,
"found": true,
"type": "trivia"
}
While the API responded a number 4e+185 (which is a double in Dart's eyes), we can achieve the same with a number 1.0 - it's still practically an integer, but Dart will handle it as a double.
trivia_double.json
{
"text": "Test Text",
"number": 1.0,
"found": true,
"type": "trivia"
}
Reading Fixture Files
We've now put fake JSON responses into files. To use the JSON contained inside of them, we have to have a way to get the content of these files as a String. For that, we're going to create a top-level function called fixture inside a file fixture_reader.dart (it goes into the fixture folder).
fixture_reader.dart
import 'dart:io';
String fixture(String name) => File('test/fixtures/$name').readAsStringSync();
fromJson
With all the fixtures in place, we can finally start with the fromJson method. In the spirit of TDD, we'll write the tests first. As is a custom in the Dart, fromJson always takes a Map<String, dynamic> as an argument and outputs a type, in this case the NumberTriviaModel.
number_trivia_model_test.dart
void main() {
final tNumberTriviaModel = NumberTriviaModel(number: 1, text: 'Test Text');
...
group('fromJson', () {
test(
'should return a valid model when the JSON number is an integer',
() async {
// arrange
final Map<String, dynamic> jsonMap =
json.decode(fixture('trivia.json'));
// act
final result = NumberTriviaModel.fromJson(jsonMap);
// assert
expect(result, tNumberTriviaModel);
},
);
});
}
As you can see, we've gotten the JSON Map from the trivia.json fixture file. This test will fail, in fact, it won't compile! Let's implement the fromJson method.
number_trivia_model.dart
class NumberTriviaModel extends NumberTrivia {
...
factory NumberTriviaModel.fromJson(Map<String, dynamic> json) {
return NumberTriviaModel(
text: json['text'],
number: json['number'],
);
}
}
After running the test, it passes! Sooo, we're cool, right? Not yet. We also have to test for all the edge cases, like when the number inside the JSON will be regarded as a double (when the value is 4e+185 or 1.0) Let's make a test for that.
number_trivia_model_test.dart
group('fromJson', () {
...
test(
'should return a valid model when the JSON number is regarded as a double',
() async {
// arrange
final Map<String, dynamic> jsonMap =
json.decode(fixture('trivia_double.json'));
// act
final result = NumberTriviaModel.fromJson(jsonMap);
// assert
expect(result, tNumberTriviaModel);
},
);
});
Oh no, now the test fails saying that "type 'double' is not a subtype of type 'int'". Hmm, could it be that it's because the field number is of type int? What if we explicitly cast the double into an int? Will it work then?
number_trivia_model.dart
...
factory NumberTriviaModel.fromJson(Map<String, dynamic> json) {
return NumberTriviaModel(
text: json['text'],
// The 'num' type can be both a 'double' and an 'int'
number: (json['number'] as num).toInt(),
);
}
...
Sure, this fixed the implicit casting error and now the test passes!

Ain't nothing better than a test which passes...
toJson
Let's now head for the second conversion method - toJson. By convention, this is an instance method returning a Map<String, dynamic>. Writing the test first...
number_trivia_model_test.dart
...
final tNumberTriviaModel = NumberTriviaModel(number: 1, text: 'Test Text');
...
group('toJson', () {
test(
'should return a JSON map containing the proper data',
() async {
// act
final result = tNumberTriviaModel.toJson();
// assert
final expectedJsonMap = {
"text": "Test Text",
"number": 1,
};
expect(result, expectedJsonMap);
},
);
});
...
Before even running it, we're again going to implement the toJson method to get rid of "method not present" errors.
number_trivia_model.dart
class NumberTriviaModel extends NumberTrivia {
...
Map<String, dynamic> toJson() {
return {
'text': text,
'number': number,
};
}
}
And of course, the test passes. Since there aren't any edge cases when converting to JSON (we're controlling the data types here, after all), this single test is sufficient the toJson method.
What's Next
Now that we have the Model which can be converted to/from JSON in place, we are going to start working on the Repository implementation and Data Source contracts. Subscribe below to become a member of growth-oriented Flutter developers and receive emails when a new tutorial comes out and more!
There seems to be an issue with the code or may be it is just me.
While implementing the fixture_reader.dart, I kept getting the following error.
“`
FileSystemException: Cannot open file, path = ‘test/fixtures/trivia.json’ (OS Error: No such file or directory, errno = 2)
“`
I later changed the code to the following and it started working:
“`
import ‘dart:io’;
String fixture(String name) => File(‘fixtures/trivia.json’).readAsStringSync();
“`
I went here just to share the same stuff. I am using Windows OS. Maybe it could be an OS stuff. I was stuck at this part of code for about to days till I found that the fixture would work with those to ways bellow:
String fixture(String name) => File(‘fixtures/$name’).readAsStringSync();
or
String fixture(String name) => File(‘../test/fixtures/$name’).readAsStringSync();
Since I checked the code at github repo and is different, I really have no idea of why! But at least I made it work.
Hey Matej,
awesome content you got there, thanks for your efforts! 😉
I am currently facing a weird issue which I can’t seem to find an answer to. When trying to test NumberTriviaModel for being a subclass of NumberTrivia I get the following error:
should be a subclass of NumberTrivia entity:
ERROR: Expected:
Actual: NumberTriviaModel:
I already tried using another matcher (isInstanceOf) but the same error gets thrown.
Any ideas?
Oh, I honestly don’t have any clue either. It’s a very straightforward test that should simply pass.
Although I’m almost 100% sure your code is correct, just double-check if all of the imports are the same and try copying the whole test and implementation files from Git Hub.
If you run through IDE its passing, but “flutter test” command and CI is failing as specified by @Tejas Sherdiwala
I’m having similar problems with the code. I’ve checked and triple checked, but each new course theres just problems.
Here you use domain and data models only for the response. How do I use those data models for request?
I got some weird error that both test case within fromJson group are failures.
package:test_api expect
package:flutter_test/src/widget_tester.dart 234:3 expect
test/features/number_trivia/data/models/number_trivia_model_test.dart 21:7 main..
Expected: NumberTriviaModel:
Actual: NumberTriviaModel:
Expected and Actual are the same but it says failure
The same thing happened with me.
ERROR: Expected: NumberTriviaModel:
Actual: NumberTriviaModel:
Figured out what was wrong in my case. My string text in the trivia.json and trivia_double.json was different (capitalisation of text) so the NumberTriviaModel object tNumberTriviaModel did not have the same exact fields which is why it was throwing that error. Fixing that removed those errors.
Thanks bro. and Thank you Matt
Thanks broth!!!
I had the same thing – I hadn’t made my json object text field (or the number) match the tNumberTriviaModel. I was confused thinking it was class types that weren’t matching, not the data inside the object itself.
Thanks!!
I also have a short question. Is there a “principle clean code architecture” for models when the response is just a status code (example when updating a password etc). Should we have an empty model & entity, should we use Future<Either> or is there something specific related with TDD principles?
Do you know any gode generation library to generate the models? I know json_serializable but this just generates the fromJson and toJson parts. I don’t want to write the full constructor by hand.
Thank you! Yours are some of the best tutorials I’ve found out there, and have been amazingly helpful. I had one question that I don’t completely understand, what is the advantage of having a model class extend the entity class? Are they not going to contain the same fields? Are models just to implement new methods? And if so, why not just include the fromJson and toJson in the entity class instead of creating a new model class?
Hello! I no longer extend models with the entity class. Instead, I create toEntity/fromEntity methods in the model.
The reason for this is, model may hold a millisecondsSinceEpoch integer, while the entity holds a nice DateTime.
Can you give an example of these toEntity/fromEntity methods please?
Something like this I think…
NumberTrivia toEntity() {
return new NumberTrivia(text: this.text, number: this.number);
}
NumberTriviaModel fromEntity(NumberTrivia entity) {
return NumberTriviaModel(text: entity.text, number: entity.number);
}
Do I unterstand your comment right that you no longer use
class NumberTriviaModel extends NumberTrivia {…}
but instead
class NumberTriviaModel {…}
which is the way I used entities/models before adapting to your tutorial…
Hello I was implementing Clean architecture for an app I am working on kept rceiving this test error … Please help
Expected: {
‘registrationNo’: ‘123456’,
‘fullName’: ‘Test User’,
‘profilePicUrl’: ‘http://placehold.it/120×120&text=image1’
}
Actual: {‘registrationNo’: null, ‘fullName’: null, ‘profilePicUrl’: null}
Which: was instead of ‘123456’ at location [‘registrationNo’]
package:test_api expect
package:flutter_test/src/widget_tester.dart 234:3 expect
test/features/dashboard/data/models/dashboard_model_test.dart 54:9 main..
Fixed it no pressure
Hey man! How you fixted? I have same problem
Expected: {‘text’: ‘Test text’, ‘number’: 1}
Actual: {‘text’: null, ‘number’: null}
Which: at location [‘text’] is instead of ‘Test text’
Hi Matt,
1. Why the need of toJson, ther’s no API accepts NumberTrivia object?
2.Wouldn’t it be better if we have centralized serializer/deserializer class which is generic, and repositories have this serializer object, that
deals with APIs response and pass the deserialized result to the domain layer?
in this case we won’t need the models classes, and use only entities.
Thanks
Hi Mado, maybe for minor cases you could use the entities in datasources, but think about it you are developing your domain model (entities) independent of data models. think you have defined a property named ‘address’ as string in entity, but datasource returns an aggregation of address: street, city, zip code. if you change your entities to match responses from datasource, now you are coupling your domain to data layer which is against clean architecture principles.
Hi, I am still wondering by setting a num to int, wouldn’t that still be an int ? why does it passed by double ?
Hi Matt Rešetár.
Resocoder is the best flutter tutorial I have ever seen.
I got an Error in this line: expect(result, testeJustificativaModel);
“ERROR: Expected:
Actual: ”
I fixed it by replacing “testeJustificativaModel” to “isInstanceOf()”.
Could you explain me why does it happen?
Thanks a lot.
ERROR: Expected:
Actual:
ERROR: Expected
Actual:
Loving the content! I’m getting confused regarding whether the information flow here is being setup for submitting or retrieving data from the server. When applying the TDD approach to my own app, I must send several pieces of information to the server and expect to get back one piece of information. At this point in the tutorials, are the repositories, models, etc. dealing with submission or receipt of data, or both?
I’ve been reviewing all previous lessons and think I have a bit better understanding.
Would it be fair to say that Entities contain the requirements of the return data, and the Repositories which implement Use Cases contain the submission variables?
For instance, if I expect to receive back something from a server api, my Use Cases define the information needed by the server, are implemented by the Repository, and the reply from the server must adhere to the Entity?
Hi Everyone! Can somebody help me? I don’t know why, but the last test fail (toJson), rewrite many times, but I guess is not a typo. It’s because null safety?
Expected: {‘text’: ‘Test Text’, ‘number’: 1}
Actual: {‘text’: null, ‘number’: null}
Which: at location [‘text’] is instead of ‘Test Text’
Hello Matt,
Thanks for putting together very nice tutorials.
Assume for a Login REST API, the API returns the access/bearer token on passing right credentials and error on wrong credentials. So, the response would like below.
Pass case:
{ “access-token” : “03fadldfvcbrsdhtyi” }
Failure / Error case:
{ “error” : “Invalid credentials” }
Any suggestion on how should we create entity and model classes for this ? If separate classes, where the check (error is present or not) should happen ?
Can I get the source code to follow along with the tutorial?
When I use
expect(tNumberTriviaModel, isA());
it returns:
Expected:
Actual: NumberTriviaModel:
Which: is not an instance of ‘NumberTrivia’
Not sure if isA is expected to use as checking the parent class.
Hey Matt,
your tutorial is really cool. Only one thing gives me a headache. Why does the model need to extend the entity? Most of the time I’m working with existing services that have a completely different structure and types than my internal entities. And since the entities are very large, I tend to have them generated.
Another point that concerns me is that you seem to use the same model for the Local and Remote DataSource. Here again the thing is that I have different data types and fields. Among other things, this is due to the fact that I usually do not develop the external API myself. For example, I have a weather API that uses coordinates to determine the weather for a city. The cities have no unique ID in the API. But if I store them in a local SQLLite DB, I need an ID. Furthermore, I need the ID also in the model, for routing. In some publications there is also a distinction between a DTO and a model?
Furthermore, it seems that some mapping classes or functions are used to build an entity from a model. How do you see this?
Maybe you can give me some hints or some food for thought.
Best regards
* And since the models are very large, I tend to have them generated.
** Furthermore, it seems that some people use mapping classes or functions to map to an entity from a model. How do you see this?
why `factory` NumberTriviaModel.fromJson? How about using `static` NumberTriviaModel createFromJson(Map json)? What would the pros & cons be comparing `factory` and `static`?
I got it. You used `factory`, so you didn’t have to create a new instance here:
`result = NumberTriviaModel. fromJson (jsonMap);`
like so:
`result = NumberTriviaModel(number: 1, text: ‘Test Text’). fromJson (jsonMap);`
In this case you had a few options:
A. `factory`: just like you did
B. Instead of creating a new `NumberTriviaModel` instance you could use the previously initialized instance `tNumberTriviaModel` and then solely naming the method fromJson (without `factory NumberTriviaModel.` part) just like you did for `toJson` method
C. Probably the best option: Make a separate file for conversions in core/utils/conversion_utils.dart:
class ConversionUtils{
static NumberTriviaModel jsonToNumberTriviaModel(Map json)=>NumberTriviaModel(
text: json[ ‘text’],
number: json[ ‘number’],
);
static Map numberTriviaModelToJson(NumberTriviaModel numberTriviaModel)=>{
‘text’: numberTriviaModel.text,
‘number’: numberTriviaModel.number,
};
}
Then when testing it, calling the static methods like so:
final result = ConversionUtils.jsonToNumberTriviaModel(jsonMap);
final result = ConversionUtils.numberTriviaModelToJson(numberTriviaModel);
This C option provides better separation of concerns, aligns with the single responsibility principle, and offers greater maintainability and potential for reuse.
Thank you for your sharing. I am worried that I lack creative ideas. It is your article that makes me full of hope. Thank you. But, I have a question, can you help me?
I don’t think the title of your article matches the content lol. Just kidding, mainly because I had some doubts after reading the article.
Your point of view caught my eye and was very interesting. Thanks. I have a question for you.