Flutter Clean Architecture Series — Part 2 (UPDATED)
Hey, welcome again in another part of this interesting series, if you haven’t read my previous part, then I strongly advise you to check it out right a way since I explained the most important basic things and facts about Clean Architecture and how to setup the starting project.
What will be explained?
In this part, we’re gonna be diving deeply into how to implement the remote side including fetching data from APIs, modeling our data objects, writing abstractions, bloc logic, and customizing our UI.
API Data
After creating your account in the News API website, head to your profile to get your own api key. Now in the documentations, they provides the following endpoints
The only endpoint that we’re gonna be using is the “top-headlines” which is of type “GET” and takes various query parameters. In the json response we can see that it contains list of article, each article contains source, and the whole data is in the main response that has totalResults and status.
I’ll be explaining everything one by one, folder by folder, and layer by layer consistently.. So lots of work and patience we should have.
Get Ready
First thing first, I’m gonna be needing you to be focused because the journey has just began and we’ve a lot of work to do, so grab your coffee and let’s get into it, shall we?
Utils Folder
We need to prepare things before we start implementing the layers, so the first thing we need is to write some resources required to implement our logic properly.
resources
In the lib/src/utils/resources create a file and name it “data_state.dart” which contains the following code:
This abstracted generic based class is very handy when we come to the fact that we’re about to communicate with network calls and APIs, but how?
This wrapper class can be used to wrap our entire network call in order to determine the state of the request being sent to the server and its response, which is so important later on when we will have too many requests and logic, you will see how minimized the code would become.
As you can see in the code, we’ve two different states, one when we get a successful response (DataSuccess), and the other is used when an error occurs while sending the request or receiving the response (DataFailed).
Domain Folder
Head to the models folder inside this domain
Models
Create the following inside the models folder:
- Create lib/src/domain/models/requests folder
- Create lib/src/domain/models/responses folder
The reason why we create such folders is because we need to separate our requests, responses, and other models from being with each other under the same folder and this makes things cleaner to work with.
Now, in the requests folder, create a new request file (dart file) and name it “breaking_news_request.dart” which contains the following:
When do we need this class? don’t forget that we’re trying to write a clean code so later on when we need to pass parameters to a function/method to get the articles from the REST API we pass this class, because a clean function/method should have 3 or less parameters, otherwise we create a class like this and pass it as a parameter to that function/method.
And here, I’m setting default values such as the “defaultApiKey” we defined in our “strings.dart” constants file and “defaultPageSize” in “nums.dart” constants file inside lib/src/utils/constants which holds the value of 20 in our case.
Breaking News Json Sample
Take a look at the json below, this is the response we get when we request the “top-headlines” endpoint
https://newsapi.org/v2/top-headlines?country=us&apiKey=YOUR_API_KEY
Now, we need to create our models according to this json.
Let’s start with the Source object, open the lib/src/domain/models folder and create a model (dart object class) called “source.dart” contains the following
You should get errors telling you that final variable must be initialized, if you remember in Part 1 where I discussed the VS Code Extensions.. I mentioned an extension that would help us generate json serialization, equality, constructor and all of it, right? It’s called:
Once you install it in your VS Code (you may need to restart it), head back to the Source class and press (Command + .) in MacOS to prompts a context menu, select “Generate data class” option in order to automatically generate the rest of the code for you.
This is the result after you generate the code, I removed the “copyWith” and “toMap” because we won’t be using them but you can leave them if you prefer.
Let’s repeat the process with the Article class too, create a file called “article.dart” besides the “source.dart” file, then add the properties according to the json sample data, like so:
You see! the Article class now contains the Source class inside it because as we noticed in the json sample data there’s a source object in each article.
Note: the property “id” isn’t listed in the json sample data, but we’re gonna use it later.
Again, put your curser on any property, and press (Command + .) then choose “Generate data class” option to generate to proper code as it shown below:
Awesome, it generated the constructor, json serialization, and equality as well.
The final class we need is a response class that holds the list of the articles as well as the other properties.
Create a file in the lib/src/domain/models/responses and name it “breaking_news_response.dart” which contains the following:
Note: some developers may prefer to use the build_runner code generation based packages in order to do the json serialization and all the stuff we did like this one called json_serializable, and it’s 100% fine whether you choose to use the extension or the package.
repositories
Ok! we’ve done creating the models our app requires, now we need to head to the repositories folder which is located in the domain, in order to create our first abstracted repository.
Create a file in the lib/src/domain/repositories and call it “api_repository.dart” which contains the following
If you remember in the Part 1, we said that the domain layer contains only the interfaces (abstraction).. the implementations will be in the data layer. So now we defined an abstracted class which contains one method that returns a Future data of type BreakingNewsResponse wrapped with the DataState to determine the state of the response.
Note: we could’ve used nullable return type like
Future<BreakingNewsResponse?>
which returns the data if the response is success and null if it fails, but dealing with nullability all the time makes the code harder to read and follow and we won’t be able to know why it failed and where exactly, so using this approach determines the state of the request/response and also enhance the code readability.
Data Folder
datasources/remote
Things are getting a little bit interesting here.. 😎
As we all know.. we’re talking about articles, news, and stuff like that.. but where is that service that provides us the data we need? Do I have to write everything my self? extends something?
ABSOLUTELY NOO!! because we’re gonna using the retrofit package that uses the dio as a network client, and first thing we should do is creating a new file inside the lib/src/data/datasources/remote and call it “news_api_service.dart” which contains the following:
Now, once you define your abstracted class you need annotate it with “@RestApi” which takes a baseUrl and a parser as shown above.
Note: if your models expose named factory constructors called “fromJson” instead of “fromMap” then you should change the parser to “Parser.JsonSerializable” instead of “Parser.MapSerializable”
It’s necessary to provide the factory constructor as it’s written above, and once you’ve done that.. we need to add our abstracted method that would be responsible of getting the data from the server, so it would be like this:
In this abstracted method, you’re basically telling the Retrofit to generate a method for you that can internally uses the Dio to make a “GET” network call to an endpoint named “/top-headlines” with the baseUrl we provided at the top of the class and also takes multiple query parameters as defined in the function’s parameters. And that’s it, you don’t need to write anything else. To generate the code, run the following command in your terminal:
flutter pub run build_runner build --delete-conflicting-outputs
And you shall see a new generated file called “news_api_service.g.dart” contains all the code it needs to get you the data according to what you’ve defined in your abstracted class.
Note: the return type here is wrapped with class called HttpResponse and this is because we need details about our request/response such as (status, message, request options, …etc) and this is very helpful for us to determine certain things once we get the response like whether the we get a successful response (200 or 201, …) or some server errors (404, 401, …)
repositories
Once you’ve done with the API service, now it’s time to implement the domain’s abstractions 😎!
Before we implement the ApiRepository class, we need to create a base class called BaseApiRepository, create folder inside the this data folder and name it base, then create a file inside it and name it “base_api_repository.dart” which contains the following:
This base class plays an important roll here, which wraps our api call that comes from the service “news_api_service.dart” and return a DataState instead of HttpResponse.
By doing so, we’re reducing the boilerplate code in each method without the need of writing “try catch” or “if” statements everywhere because you only need to pass the request in the “getStateOf” method and this should do the rest and return the result based on the generic “T” type.
Note here that the DioError class provides us with the error type such as (connectionTimeout, badCertificate, badResponse, …etc) and each type describes a different message which can be very helpful to determine what is the error exactly. READ MORE HERE
Now, with that being said.. let’s implement our domain’s ApiRepository, create a file in lib/src/data/repositories and name it “api_repository_impl.dart” which contains the following:
As we can see, our ApiRepositoryImpl extends the functionality written in the base class and also implements the ApiRepository declarations.
It also clear to see that this class depends on NewsApiService which we’ll talk about later, but take a look at the method implementation and how easy and clean it is, we’re passing the api service request “_newsApiService.getBreakingNewsArticles(…)” to the “getStateOf” which holds data type of “BreakingNewsResponse”.
This complicated process of having many classes and abstractions gives us the advantage of having multiple implementations without interacting with domain or the presentation layers.
Presentation Folder
Blocs (Cubits)
We talked about blocs and cubits in Part 1, Remember? now, each bloc/cubit should solve/do one and only one problem/task. In our case we have to get a remote data and then display what ever the result is in the UI (emit states to the UI).
Now before getting into our cubits, we need to create base class that will help us with reducing the boilerplate code and make the code cleaner and simpler to read and understand.
Create a new folder under the cubits folder and name it “base”, and inside the base folder create a file called “base_cubit.dart” which contains the following:
Basically, this abstracted generic-based class help us to determine whether our cubit is busy or not and also holds the data being processed. The “S” type represents the state of the cubit, and the “T” type represents the internal data that maybe used. This way, the cubit class will has less code and we can easily check if our cubit is currently running a process inside or not.
Below, you will see how this base class helps us a lot
Let’s go back to our cubits, Create a new cubit in the lib/src/presentation/cubits folder and give it the name “remote_articles”.
Creating a cubit can be little bit hard, but luckily you can install and use a VS Code Extensions that helps creating Blocs/Cubits faster and easier. This extension provided by the same person who invented the Bloc/Cubit. After installing it (you might need to restart the editor), right click on the cubits folder and you should see an option at the end called “Cubit: New Cubit” click on it then specify the name “remote_articles” and congrats your cubit has been created easily.
Now, open the “remote_articles_state.dart” file and change its contents to this
Our states:
- RemoteArticlesLoading: used to indicate that the cubit is running a process (fetching api data) inside and needs to wait till it completes.
- RemoteArticlesSuccess: used to indicate that the cubit has finished with a success results (list of articles and no more data flag indicator).
- RemoteArticlesFailed: used to indicate that the cubit has failed to complete the process, with details on how it failed (DioError instance) .
Note: the property “noMoreData” is used to indicate that there’s no more articles to be loaded, so that the UI can know and stop displaying progress indicator at the bottom of the scrolling widget.
Once you finish writing the states, head to the “remote_articles_cubit.dart” file and change its content to the following:
Let’s explain everything step by step, the first thing we see is that this class extends the BaseCubit class we defined earlier which takes two generic data type:
- First: The state “RemoteArticlesState” which is the state of the cubit.
- Second: The data being used “List<Article>” in this cubit.
And in order to get the data we need, this class needs a dependency of type “ApiRepository” which contains our API call to the breaking news.
Notice here, we’re initializing our super class with an initial cubit state (loading), and initial data (empty list).
Now, if you remember that our API request we defined previously is a paginated-based request (pagination mechanism with page and pageSize) so we need to define a page variable in order to update it when we request another page to be loaded.
Below, the method “getBreakingNewsArticles” will be called directly from the UI, and its internal implementation will call the “getBreakingNewsArticles” that belongs to the repository in order to get the articles we need. And as you can see, it’s pretty much clean, and easy to read and understand because of what we did in the Domain and Data Layers. The “run” method will mark the cubit as busy since it’s running a process inside, and then we do a small check whether the response is a successful one or it failed at some point. In both cases, we’re emitting a different state to the UI depending on the response status and the UI would listen to these state changes and build its UI accordingly.
UI (views)
Before we create our UI files, let’s talk a little bit of what are the hooks? and why do we use them?
I said before that I’ll use the flutter_hooks packages but now we should explain it in details.
Hooks make it easy to write reusable and composable code, as they allow you to encapsulate logic and state within individual Hooks and then combine them as needed. Additionally, Hooks can help to reduce the amount of boilerplate code needed for common tasks such as managing state and handling lifecycle events, which can lead to more concise and maintainable code.
And flutter hooks provides a set of awesome hooks that we can use once we install the package, for example instead of making a StatefulWidget for building a widget that uses the AnimationController with (initState, dispose, ..etc) setup, we can simply use (HookWidget class and useAnimationController hook) inside the build method. We can go deeply explaining how hooks are powerful, but the main docs provides a pretty good explanation.. if you still curious about it, READ MORE HERE.
Let’s create a file inside the lib/src/presentation/views and call it “breaking_news_view.dart” which contains a pretty long piece of code (JUST KIDDING):
The “BreakingNewsView” acts as the home page, so later on we’ll add this view to our router configuration. This view depends on the “RemoteArticlesCubit” that we defined, so we‘re gonna discuss how we provide this cubit in our applications’s widget tree.
The “useEffect” acts as the “initState” once it gets called, it won’t be called again unless we provide properties to listen to and the we can return a function which works similar to the “dispose” method. Currently, we’re initializing our scroll controller with a listener to listen to the scrolling and notify us once the user reach the end of the scrolling widget in order to call the “getBreakingNewsArticles” for fetching new articles to the UI.
In the Scaffold’s body, we listen to the Cubit’s states and we build our UI accordingly as shown in the code above, for instance when we get a “RemoteArticlesSuccess” state, the BlocBuilder widget instantly rebuild its content and returns the below define method “_buildBody” which contains a scrollable list of the gotten articles along with the loading indicator below the list.
If you noticed, the method in the “scrollController” that named “onScrollEndsListener” which takes a callback isn’t really exists in the “ScrollController” class, because it’s an extension method we defined on top of the existing class and to do so, create new file under lib/src/utils/extensions and call it “scroll_controller.dart” which has the following:
This extension method listens to the scrolling changes (whether the user scrolled to the end of the scrolling widget or not) and call its callback if it does. This reduces the boilerplate code written in our view without duplicating the code every time you need something similar to this.
Finally, we need to add this view to our router, head to the “app_router.dart” file and add the view as follow:
This is the home page (root) of our application, so you must set the “initial” property to true to set it as your landing page, run the build_runner command and you’re to go.
flutter pub run build_runner build --delete-conflicting-outputs
Dependency Injection (Service locating)
We’ve completed most of the code so far, but one thing left to do is to prepare our dependencies to be located (injected).
So, create a file in the lib/src/ and call it “locator.dart” which contains the following:
First, create a global instance of the GetIt class which is gonna hold all the dependencies we need. Inside the “initializeDependencies” we define an instance of “Dio” and to make things awesome? Add the log interceptor that I made (available here awesome_dio_interceptor) to the your dio interceptors list in order to log the request/response in details with beautiful and colorful text.
Next, we register “Dio” as a singleton as well as the “NewsApiService”, and the “ApiRepository”.
Notice here, if you ever wanted to change the implementation of the “ApiRepository” with a different one, all you need to do is to declare its abstraction in the Domain Layer, then implement it in the Data Layer, and replace it here with the new one and that’s it, you don’t need to interact with the UI or the entire presentation implementation. This is the beauty of having the Clean Architecture in your app which makes life much easier.
With that being said, all we have to do now is to call the function “initializeDependencies” in the main function to setup everything before running our app, like so:
And one final step left, is to provide our “RemoteArticlesCubit” class to the application’s widget tree, like so:
To do that, we’re wrapping our app with a widget called MultiBlocProvider in which it provides our cubit globally to our app and we can call that cubit through the context from anywhere in the app.
And as you can see, our Cubit depends on the “ApiRepository” and we’re providing it using the service locator we defined above.
Note: when we’re providing the cubit, we’re immediately calling the “getBreakingNewsArticles” using the cascade notation, which is a lovely dart feature that I always use. for more please READ HERE
Run
Now go ahead and run the app.. and congrats your app should get its data from the server perfectly 😎.
If you’ve successfully ran your app, then congrats for the GREAT job you’ve made, if not and I’m sorry for any error you’re encountering, but please be patient and try to read the article again you might have missed something and I’m pretty much sure you‘ll figure it out, TRULY!
What’s Next?
Part 3 will explain and implement the local data (final part).
Github Source
Consider giving the repo a pretty small star ❤️ for a little support… and see you next pal.. 😄