Flutter TDD Clean Architecture Course [2] – Entities & Use Cases

58  comments

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.

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!

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.

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 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.

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.

Testing without concrete implementation of classes is possible with mocking. A popular package for this is caled mockito, which we've added to our project as a dev_dependency.

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".

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.

Test files written using TDD always map to production files and append a "_test" at the end of their name.

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!

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a freelancer and most importantly developer educator, he is set on helping other people succeed in their Flutter app development career.

You may also like

    • 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

  • 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’.

  • 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];
      }

  • 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.)

    • 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

  • 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?

  • 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

      • 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.

  • 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.

  • 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’.

  • 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);
    },
    );
    }

  • 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?

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