Placeholder Image

Subtitles section Play video

  • 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, which you can see on the side right now, for presentation, domain, and data.

  • This is for the Number Trivia app we are building.

  • And now it's time to start filling in these empty folders with actual code, of course, using test-driven development.

  • Hello, welcome to ResoCoder, where you are getting prepared for real app development by building better, faster, more stable apps.

  • So subscribe and hit the bell if you want to grow your coding skills.

  • So, of course, whenever you are building an app, you should start with the UI and user experience first, but I've done that homework for you.

  • As you've seen in the previous tutorial, the app looks something like this.

  • The app looks like this, the actual coding process, though, will happen from the innermost stable layers and will progress toward the outer layers.

  • So in the case of clean architecture, as it was proposed by Uncle Bob, we can see that we should start from the domain layer, particularly from entities.

  • So we are going to create an entity right now.

  • Before we do that, though, before we can create an entity for the number trivia, we have to add some packages over to pubspec.yaml, because we are going to be using quite a lot of packages to make things easier for us, of course.

  • And with that, I have to tell you to make sure you check out the written tutorial from the link in the description, where you can find all of the code written in this video, all of the links, and all of that good stuff.

  • And go through this lesson at your own pace.

  • This pubspec.yaml file is one of the few things which I'm going to just copy and paste over on video so that I don't bore you to death.

  • So let's just delete all of these unnecessary comments.

  • We'll just clutter up the pubspec.yaml file.

  • And then the dependencies and dev dependencies will be the following.

  • So we have getit, which is the service locator.

  • I've added all of the dependencies right now so that we later do not have to come back to this yaml file and just lose time on that.

  • So we have getit.

  • Then we have flutter block for state management, equatable for value equality.

  • Then we are going to touch on some functional programming concepts.

  • Then for remote API, we have connectivity and HTTP packages, of course.

  • And for local cache, we are going to be using just simple shared preferences.

  • As for dev dependencies, these are the ones which do not get packaged together with your app once you build it.

  • We have flutter test.

  • This was here even before I think by default.

  • And then we have Mockito for creating mocks.

  • We are going to get to all of this as we progress forward with this tutorial.

  • But just so that you have all of the packages already present, we've added them over here.

  • And I'm just going to delete all of the other comments so that they don't clutter up pubspec.

  • And here we go.

  • The pubspec.yaml file is completely finished.

  • All right.

  • So let's now move on to creating the actual entity.

  • The first question we have to ask ourselves is what kind of data will the NumberTrivia operate with?

  • Well, of course, with NumberTrivia entities.

  • So we can create a new file under the domain entities.

  • Let's call it NumberTrivia.dart, of course.

  • And now we have to take a look at the JSON response from the API because based on that response, we are going to model our class here.

  • So if you go to numbersapi.com forward slash 42 or some other number and then question mark JSON, so it's a query parameter, you will obtain the following response.

  • And of course, this link is also available from the written tutorial.

  • So this is the response which we will be operating with throughout our app.

  • And we need to model our NumberTrivia entity so that it can contain this kind of data.

  • So what do we actually really need from this JSON object?

  • Well, we need the text, obviously, because that's the core, so to say, functionality of our NumberTrivia app.

  • We are going to store the number, but then we have two interesting fields, which are found and type.

  • We are not going to need those at all because found is completely irrelevant for our case, and the type will be always trivia.

  • And why is found completely irrelevant?

  • Well, if I search for a number other than 42, so for example, some complete gibberish long number, which doesn't have any entry in the numbers API and search for that, you can see that found is false, but we still obtain some kind of a reasonable response that this is an uninteresting number.

  • So we can still display even this message to the user in the app.

  • We do not need to worry at all whether or not the number was found.

  • So the bottom line is that we need the text and number fields to map to properties in our app.

  • So let's create a class, NumberTrivia, which will extend Equatable.

  • And let's import Equatable here.

  • This is so that value equality is made simple because by default, Dart supports only referential equality.

  • So even two objects which contain completely the same data will not be equal unless they are referencing to the same point in memory.

  • With Equatable, this is completely changed.

  • When two objects contain the same values, the same data, they will be equal.

  • And we do not need to override equal operator or hash code, anything like that.

  • Equatable does that for us.

  • So it saves us a bunch of boilerplate.

  • And now we need to store the text and number.

  • So final string text, and then final int number.

  • And of course, we need to put them into constructor.

  • So we can just hit control dot, at least in VS code, and create constructor for all of these fields here.

  • But we are not just going to have this kind of a constructor with unnamed arguments.

  • We want to have them named.

  • And of course, we are going to make them required.

  • So we need to import meta package.

  • So meta dot Dart.

  • And let's just make these required.

  • And also this one will be required here.

  • And we mustn't forget to pass these over to the super constructor, because Equatable needs to know which properties to make equality happen on.

  • So we are going to pass them in as a list.

  • So we pass in text and also number to the super constructor, which is Equatable.

  • And you may have noticed that we didn't do any test driven development here.

  • That's because this class is really, it doesn't have any logic.

  • There is why we aren't writing any tests for this number trivia entity.

  • Should you have any logic inside your entity, you would write test even for entity, but that's not the case with this one.

  • All right, now let's talk about use cases.

  • They are where the business logic gets executed.

  • And sure, there won't be much logic in the number trivia app, because all a use case will do is to get data from a repository.

  • So just so you can see that, here we go with the diagram.

  • We have use case here.

  • It will get data from the repository and they will communicate together through these entities.

  • And we have created just one such entity called number trivia a few moments ago.

  • And we are going to have two use cases in our finished app, getConcreteNumberTrivia and getRandomNumberTrivia.

  • By looking at this diagram, as I've already said, you know that use cases get data from repositories, and then they pass that data, which in this case are entities, over to the presentation logic holders and to the whole presentation layer.

  • So it kind of makes sense that when a use case is communicated with repositories, the repository should return an entity, right?

  • But because we have to allow for asynchronous operations, we are going to wrap this entity, which in this case is number trivia, into a future.

  • But that's not the actual case of what we are going to do.

  • We have to think about error handling as well, because is it the best choice to let exceptions freely propagate?

  • Having to remember, and when you have to remember something, you know that that's not cool.

  • We have to remember to cache them somewhere else in the code if we let exceptions just nicely propagate at their own peril.

  • I do not think that this is the best choice.

  • Instead, we want to catch exceptions as early as possible inside the repository here, and then return failure objects from the repository methods.

  • So unlike exceptions, you will not have to try and catch all of these exceptions.

  • You will have failure objects, which are returned as regular objects, return types from methods.

  • There will be no special error flow, nothing like that.

  • It will be just like a regular data flow without any kind of try-catch-finally blocks in the rest of the app from the repository upwards.

  • So to recap, repository and then subsequently use cases will return both number trivia objects, and also they will return failure objects from their methods.

  • So how is something like that possible?

  • How can we return number trivia or failure from the same method?

  • Welcome functional programming, and that's why we have imported the darts package over to our app.

  • This functional programming darts package allows us to simply use an either type.

  • While I will not pretend that I am some functional programming pro, at least not yet, you don't need to know any really advanced stuff about either type, which is a functional programming concept, will allow us to return either number trivia or failure, which we are going to create next.

  • Obviously, we still do not have any failure classes.

  • So let's define them right now.

  • We are going to create an error subfolder under core, and this will hold failures.dart.

  • And the failure here will be really only a simple class.

  • It will be abstract actually, because then later on, we are going to create some concrete implementations of failure objects.

  • And again, we are not writing this in test-driven development manner, because there is nothing to test in an abstract class.

  • So this will be abstract class failure.

  • Let's again extend equitable, so that the class is extending failure later on, should they have any fields like message or some error code or something like that, their equality will be able to be checked based on those fields.

  • So for that, to use equitable in this abstract base class, we have to have a constructor here, which will accept a list of properties, which will equal const dynamic, which will be an empty list.

  • And then we will pass this list of properties over to the super constructor.

  • This is nothing really to worry about.

  • This is just what's going on with this Dart analyzer.

  • This is just simply the way that things work with equitable.

  • So we pass in properties over to the super constructor.

  • And again, this is only an abstract class.

  • We are going to create concrete classes in the next parts of this course.

  • Then as you hopefully remember from the last part, and as is signified by this diagram here, a repository from which a use case gets its data is both inside the domain layer and inside the data layer as well at the same time.

  • Or to be more precise, the definition of a repository, or as we are going to call it, a repository's contract is present in the domain layer.

  • This upper half of this gradient is pink or what is this color or whatever.

  • And then the concrete implementation of a repository, this green side of the gradient is in the data layer.

  • This will allow for total independence of the domain layer.

  • But there is also another benefit, which we haven't talked about yet.

  • And that is testability.

  • That's right.

  • Testability and separation of concerns go together extremely well.

  • And this is the absolute beauty of clean architecture because it really complements test-driven development because without architecture, you cannot even test anything because spaghetti code, believe it or not, cannot be tested.

  • How will this allow for this?

  • This will be an abstract repository class, will allow us to write tests, test-driven development style, of course, for the use cases, even without having an implementation of the repository.

  • Something like this is called mocking.

  • And we are, of course, going to get to that later on.

  • For now, let's create the so-called contract of the repository.

  • Inside the domain folder, under repositories, let's create a new file, NumberTriviaRepository.dart.

  • And inside of here, we will have an abstract class, NumberTriviaRepository.

  • And this class will have the following interface.

  • We want to have two methods here, one for getting concrete number trivia, for which we have to pass in a number.

  • And don't worry, I will come back to the return types in just a bit.

  • So getConcreteNumberTrivia, int number.

  • If I could write number.

  • And then, of course, it's going to have getRandomNumberTrivia, which will not have any parameters.

  • So let's just rename concrete to random.

  • And what is going to be its return type?

  • Well, we are going to use that either type, which I have described previously.

  • So let's go bit by bit.

  • It will, of course, still return a future, because it will be an asynchronous operation to get the concrete or data sources.

  • And what's interesting is the type which the future will wrap.

  • It will be either, which comes from the darts package.

  • So let's import that.

  • And then, either gets two type parameters.

  • One is left, so L, and R for right, of course.

  • And the left side is always, for this kind of error handling, the failure.

  • So let's import failure here.

  • And the right side is always the success kind of data.

  • So in this case, it will be the entity number trivia.

  • And we, of course, need to import even that right over here.

  • And then this return type will be the same for getRandom number trivia.

  • We will cover this in greater detail later on, how to actually work with this either type.

  • But for now, just know that this function, this method, will return either a failure, which is the left side, or number trivia, which is the right side.

  • And this way, we do not have to deal with catching exceptions anywhere else in the app than in the repository, which will convert the exceptions into failures.

  • And let's use the nifty little extension for VS Code to fix import, so they will be in the relative manner.

  • Okay, although this part is getting quite long and information-packed already, I do not really wanna leave you hanging.

  • So we are finally going to write some tests while implementing the getConcreteNumberTrivia use case.

  • And in the next part, we are gonna add the getRandomNumberTrivia use case.

  • So definitely stay tuned for that and subscribe to this channel if you do not want to miss it, and also hit the bell button, so that you will get notified about all the new videos.

  • As is the case with test-driven development, we are going to write the test before writing the production code.

  • So this will ensure that we aren't gonna add a bunch of unneeded functionality.

  • This is the YAGNI principle, you ain't gonna need it, which is one of the most powerful things about test-driven development, because it forces you to really think about what you write, because you really do not want to test unnecessary logic.

  • It even doesn't make sense.

  • You cannot write unnecessary logic with test-driven development, because you really just do not even think about writing something for future-proofing your code.

  • And that is the perfect thing about test-driven development.

  • So let's get right to it.

  • Writing the test in Dart apps happens inside the test folder.

  • We have a widget test here, we can delete that right now.

  • So move to trash.

  • And usually, what we are going to do too, is that test folder structure follows the production code folder structure.

  • So we are going to create all of these folders.

  • So core will go here.

  • Then also new folder features.

  • Inside the features folder will be another folder called number trivia.

  • And right now, we are going to write only the use case, so the domain folder would be enough to create here.

  • But we are going to create all of the other folders as well, so also data.

  • And then presentation, just so that we have everything we need for future lessons.

  • So we have data, domain presentation.

  • Inside the domain, we are going to have the use cases folder, and this is where we are going to write our first test.

  • Tests in Dart are always named the same way as is the production code file, but we append test to the end.

  • So because the production code file will be called get concrete number trivia, the test will be called get concrete number trivia test.dart.

  • And again, you can get all of this code from the written tutorial, which is available from the link in the video description.

  • Writing the test requires a bit of a setup.

  • We know that the use case should get its data from the number trivia repository, for which we currently have only the contract, the abstract class.

  • Because we have only the abstract class, we are going to mock it so that we can add some functionality to it only for this test.

  • And mocking something also allows us to check whether or not some methods have been called on that object.

  • And overall, it really allows for nice testing experience.

  • So without further ado, let's create a class, mock number trivia repository, which will extend mock, which comes from the makito.dart package.

  • And then it will, of course, implement first, it needs to implement the number trivia repository.

  • Let's import even that.

  • So extending this new class with mock allows us to mock it, of course.

  • And then we implement the interface from the number trivia repository abstract class.

  • Let's now also import the flutter test package.

  • So flutter test.dart will go here.

  • And now we have to think about how will the use case, which we currently do not even have a file for, and that is perfectly fine with test-driven development, how will the use case operate with the repository?

  • Well, of course, it's going to get that repository passed in through the constructor so that later on we can use the getit package to do some nice dependency injection thingies.

  • This is called loose coupling, and loose coupling is absolutely crucial for test-driven development, because without loose coupling, you cannot test anything, basically.

  • And to pass this mocked version of a repository into the now not yet existent use case, we are going to use a method called setup, which is available for every test that you write in Dart.

  • And the setup method runs before every single test.

  • So let's first create void main, because all of the tests run inside this main method.

  • And then we have the setup method here, in which we are going to initialize all the objects we need.

  • But first, we actually have to create the variables in which the objects in question will be stored.

  • So what do we want to have here?

  • Well, the now not yet existent get concrete number trivia use case.

  • So we are going to just call it use case.

  • And then also the mock number trivia repository will be stored here, so that we can then later on get it and do some mocking on this instance.

  • So inside setup, we're going to first initialize or instantiate, to be precise, the mock number trivia repository.

  • So let's create a new instance, just like that.

  • And then we're going to also instantiate the use case.

  • So get concrete number trivia use case will be instantiated, and the mock number trivia repository will be passed in.

  • Of course, it doesn't make any sense to continue with writing the test, because currently, it doesn't even compile.

  • So even before writing the actual test, we can do another step in the test driven development, because we have arrived in the red phase.

  • TDD works in red, green refactor phases.

  • Red phase is obvious, because we have red squiggly lines.

  • So we have to fix something in the red phase.

  • And now we want to arrive into the green phase.

  • And then later on, we will refactor.

  • And we are going to arrive into the green phase, which is without errors, by creating the actual get concrete number trivia class.

  • So let's create it under domain use cases here.

  • So new file, get concrete number trivia dot dart.

  • And this will be just a simple class for now, get concrete number trivia.

  • And it will accept a final number trivia repository called repository into its constructor.

  • So once we import it and create a constructor for missing fields, and fix import so that they are relative, this is the kind of class we have now.

  • And this is perfectly enough for this test to not complain anymore about non-existent get concrete number trivia use case.

  • And I probably have a typo here.

  • Of course, get concrete.

  • So we have to rename that concrete.

  • Now we are talking.

  • So now we can test it, anything yet.

  • So let's move on right on to that.

  • The nature of our number trivia app is really simple.

  • So there will not be a lot to test here in the use case, because really, all it will do is to just get the number trivia data from the repository.

  • And that's basically about it.

  • It will just get the entity or the failure, of course, from the repository.

  • To start writing a test, you can either just write test, and then write should get trivia for number from the repository.

  • Or I have a nice little snippet, which I have created myself, and that is AAA test, which is the arrange, act and assert test.

  • So this test should get trivia for the number from the repository.

  • And now, of course, we have already the repository instance present in here, which is the mocked instance of the repository.

  • But we do not have the trivia and we do not have a number.

  • So let's these variables, we want to have final T number, which is the test number, which we are going to try to get from the repository.

  • So T number is equal to just one.

  • And then final T number trivia is equal to a new instance of number trivia.

  • This is what is going to be returned from the repository.

  • We want to have a regular instance of it here.

  • So it accepts a number, which will be the T number, or we can actually just pass one in here.

  • It doesn't really matter.

  • And then the text will be again, it doesn't matter what the text is.

  • So now in the arrange phase of this test, what we want to do is to provide some functionality to the mocked instance of the repository.

  • So when, and this is really nice, the syntax that is here, because you can really read it as an English sentence and it will all make sense.

  • So when mock number trivia repository, get concrete number trivia is called on it with any argument.

  • So it doesn't matter whether or not the number is one or two or three, it can be really anything.

  • So any is a matcher.

  • Then answer, because it's asynchronous, so you cannot call then return, but you have to call then answer.

  • And the answer will be asynchronous.

  • And what do we want to answer?

  • Well, the return type of get concrete number trivia is an either and the right side of the either type is number trivia and the left side is failure.

  • Well, in the test, it doesn't really matter what we return.

  • We can return really either failure or the number of trivia.

  • In this case, let's return the number trivia.

  • So we want to return the right side of the either type.

  • This is how we really use it.

  • And then later on, you can also check whether or not the side of the returned object is left or right.

  • If it's left, you know that it's a failure.

  • If it's right, you know that it's a successful object returned.

  • So right, let's import darts here.

  • We want to return right the number trivia.

  • So to go through this again, when the mock number trivia repository, get concrete number trivia method is called with any argument, any number, then always answer with the success response, so to say.

  • So the right side of the either with number trivia contained inside of it.

  • All of this is available from the written tutorial.

  • So definitely, if you want to learn at your own pace, check that out from the link in the video description.

  • Now we move on to the act phase of the test, which is usually really simple.

  • So we just want to store the result of calling the use case.

  • And we want to call the use case, which is asynchronous.

  • So we want to call await use case dot execute.

  • And of course, there is no such method yet, because we have not implemented it, because we are doing test-driven development.

  • So we want to call execute on that use case and pass in the number, the number here.

  • Now comes the assert part of the test.

  • So what do we want to check for?

  • Well, we expect that the result of the execute method on the use case will really be the same thing as was just returned from the mock number trivia repository, because again, the stuff that use case does is really simple.

  • It just gets the data from the repository.

  • And in the case of number trivia app, it doesn't do anything more than that.

  • So therefore, we expect the result to be right.

  • And inside of it, we expect that there will be the number trivia present.

  • And then we also want to verify using the Mockito package.

  • We want to verify that mock number trivia repositories get concrete number trivia was called with the number.

  • This is pretty important, because imagine that we pass in the number into the use case.

  • So just to make it more visually concrete, let's pass in number 20.

  • But somehow in the use case, some smart guy decided that he will always pass the number 42 over to get concrete number trivia in the repository.

  • If we did not have this verification here, we would find out about it, of course, because the response would be not correct.

  • But this verification adds another layer of protection to our code against these kinds of errors, which do not just pass the correct arguments to the methods of the dependencies of the use case or any other class there is.

  • So let's again, revert back to the number.

  • And finally, we are just going to verify no more interactions are happening in the on the mock number trivia repository, because once we call execute, the use case should not do anything more with the repository.

  • There is again, no point in even running this test, because it's not going to run since it doesn't even compile.

  • We have a compilation error here that execute is not present on the use case.

  • This test really acts like a documentation for the functionality, which we are about to implement.

  • So let's jump right into that.

  • We're going to go to get concrete number trivia and start writing the implementation of execute method.

  • So it will return future either, which contains failure or number trivia.

  • So we need to import all of these types over here.

  • Once we give this method a name, execute.

  • So let's import darts, let's import failures, and let's import number trivia.

  • Now based on the test, we know that execute method should take in a number, which is a named parameter.

  • So therefore, we know what we need to do.

  • It will have a named required, of course.

  • So we need to import meta also, meta.dart, required number, which is an integer.

  • And it's going to just call the repository that get concrete number trivia with the number.

  • And of course, we need to return a weight, whatever was gotten from the repository.

  • So this function needs to be itself asynchronous.

  • All right.

  • So this is the implementation for the execute method.

  • And now all we need to do is to run the test.

  • I have a handy shortcut, keyboard shortcut for running all of the dart tests.

  • So I will show you how to do that.

  • If you are using VS Code, of course, in IntelliJ, this would be completely different.

  • But in VS Code, go to file, preferences, keyboard shortcuts, and now search for dart run all tests.

  • And for this command, just set some key binding.

  • I have shift, alt, and left square bracket.

  • So once you have this shortcut, you can run all of the tests with shift, alt, and left square bracket.

  • We have only one test, of course.

  • And it's going to pass.

  • So we should get trivia for the number from the repository.

  • This is only a simple test.

  • That's because the functionality of the get concrete number trivia use case is also simple, but it in our code.

  • And the other way to run a test is to go over to the debugging tab here in VS Code.

  • Now tap on this configure or fix launch.json here icon.

  • Ignore .NET if something like that pops up.

  • And we want to add configuration.

  • So dart run all tests.

  • All right.

  • And with this configuration present in the launch.json file, you can now select dart run all tests from the dropdown in the debug tab of VS Code.

  • So once you do that, you can run all tests even by clicking this green start debugging button.

  • And again, it will pass because the same test was run again.

  • In the next part, we are going to add another use case for getting the random number trivia.

  • And also we are going to refactor this code and also the new use cases code so that it will be more robust.

  • So definitely stay tuned for that.

  • And if you don't want to miss the next part and also more tutorials like this, definitely subscribe to this channel and also join the notification squad by hitting the bell button to make sure you grow your Flutter coding skills because here on ResoCoder, I am determined to provide you with the best app development tutorials and resources out there.

  • If this video helped you with understanding at least just a bit test-driven development process, and do not worry, we are going to write many, many more tests throughout this series.

  • Give this video a like and also share it with our developers who will surely benefit from it as well.

  • Leave a comment if you have anything to say, any questions regarding this test-driven development, I will try to answer them to my best ability.

  • Follow me on Instagram, I go under the name ResoCoder everywhere so that you get some behind-the-scenes news from what I am currently doing, and see you in the next video.

In the first part, you learned the core concepts of clean architecture as it pertains to Flutter.

Subtitles and vocabulary

Click the word to look it up Click the word to find further inforamtion about it