New article means new journey 😎!!
Hey, welcome again in another part of this interesting series, if you haven’t read my previous part then I advise you to check it out right a way because I explained the most important basic things and facts about Clean Architecture and how we setup the starting project.
What this part is about?
In this part, we gonna be diving very deeply into how we will implement the remote side.. what’s that mean?
Well, it means we’re not gonna be talking about the local database but instead we’re gonna be talking about the APIs, how to deal with them, how to setup things in each layer until we display results in the UI.
The only endpoint that we do care about and we’ll use 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.
Let’s go to the core folder because we need to create a bunch of files/folders and I’ll explain each file/code and its purpose. Create a new folder in the core called params and inside it create a file and call it article_request.dart which contains the following code
What’s the use of this? 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.
Create another folder inside the core and call it resources, then create inside the resources a file called data_state.dart which contains the following
A generic DataState class? what’s the use of that?
We’re communicating with remote API service, every request we make to the server we get response.. but what if something goes wrong? A network error occur?
Here comes the wrapper class, which wraps our entire network response with DataState so we can have two states either the response is a successful or failed with error of type DioError.
Create another new folder in the core and call it usecases and inside it create a file and call it usecase.dart which contains the following
I’ll explain what’s the UseCase, But ever wondered what’s a callable class? 🤔
Well, a callable classes are those classes that contains implementations of a method called “call” and this “call” method itself responsible for making the instance a callable instead of calling method belongs to that instance. Here’s an example
So, now I hope the idea behind callable class is clear. But wait a second.. what’s this abstracted UseCase class?
This is the representation of our Use Cases, and this abstracted class takes a type T and params P.. The type is what the “call” method will return, and the params is the parameters that the “call” may require (can be set to void if no params are required).
Open the domain/entities folder and create an entity (dart object class) called source.dart contains the following
Then, in the same folder create another entity called article.dart contains the following
Both entities are extending the Equatable class features (quality comparison, toString, …etc) and those entities are the internal representation of the app we’re building.
Ok! let’s create a file in the domain/repositories and call it articles_repository.dart which contains the following
If you remember in the Part 1, we said that the domain layer contains only the contract (abstraction).. the implementations will be in the data layer. So now we defined an abstracted class which contains one method of type Future and return data of type List<Article> wrapped with the DataState to know the response status.
Now, create a file in the domain/usecases and call it get_articles_usecase.dart which contains the following
As we’ve talk about usecases, here we’re creating a class that implements the abstracted representation UseCase class so that we can say GetArticlesUseCase class is of type UseCase, which takes DataState<List<Article>> as a return type and ArticlesRequestParams as a parameters for the overridden “call” method.
And as you can see here, this class depends on the ArticlesRepository class but we’re not instantiate it inside this usecase class.. instead we will later on inject this dependency using the get_it.
All we wrote so far are entities and abstractions that’s all, the real implementations will be in the data layer. And any changes happen in the future inside the data layer.. it won’t affect this domain layer.
Ok, why do we need models? well.. let’s first create a bunch of files, then I’ll explain more. Create a file in the data/models folder and call it article_model.dart which contains the following
Currently, this code will gives you en error since we didn’t create the SourceModel class.. let’s create it in the same folder data/models and call it source_model.dart which also contains the following
Ok! what’s going on here…!!!
No thing fancy, we’re just creating models that extending their properties from the super class (entity).. and those models classes are the ones that are responsible for the serialization which contains the (fromJson, toJson).
By this, we have confident that any changes may happen in the future (changing the serialization method from JSON to XML) won’t affect anything in the domain entities, we will only change stuff here in the model.
Let’s create one more model which will be the response it self, create a another file in the data/models and call it breaking_news_response_model.dart which contain the following
And with that, we finished preparing the models.. here I should point that I’m using this extension that helps me creating this json serialization.
Note that this extension will not create the casting types, and you’ve to do that by your self.. if you feel uncomfortable using this, then consider using the json_serializable package that automatically generates code for you.
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 use the retrofit package that depends on the dio http client and first thing we should do is creating a new file inside the data/datasources/remote and call it news_api_service.dart which contains the following
After writing this piece of code, it definitely gonna show bunch of errors.. why? because we have to run the build_runner to generate the proper code for us. And to generate that code run the following command in the terminal
> flutter pub run build_runner build
After that, everything should be just fine.. but what is that? annotations? abstractions again?
This is why I’m a fan of the retrofit, because you don’t have to worry about anything anymore.. we define an abstracted class (our news api service) and we annotate that class with @ RestApi (baseUrl: ‘YOUR_BASE_URL’) which tells the generator that this class is an API Service and will use the retrofit package, then we create a factory constructor which takes the dio as an http client and also the baseUrl. And inside that class we create our abstracted methods (it’s like telling the code generator how it should generate the code according to those annotations) so for example our only defined method getBreakingNewsArticles(…) is annotated by @ GET(‘ENDPOINT’) which tells the generator that this will make a network request of type ‘GET’ through the base url and the provided endpoint and it takes a parameters that’s also annotated for each by @ Query(‘PARAMETER_NAME’) and also tells the generator that this endpoint takes a query parameters which will be sent in the request.
Note here that the return type of this method is an HttpResponse of type BreakingNewsResponseModel (we could simply return the BreakingNewsResponseModel) but the use of that is because we need the whole http response that contains the (status, message, request, …etc) so later on we can identify whether this is a successful response or not.
It’s time to implement the abstractions 😎!
Create a new file in the data/repositories and call it articles_repository_impl.dart which contains the following
This is the implementation of what we’ve defined in the domain/repositories if you remember, this class depends on the NewsApiService class that we defined earlier.. again we’re not instantiate it directly inside this class because we will inject this dependency later.
Here comes the use of the DataState we talked about earlier, now we’re making a network request and if it goes well, it will return DataSuccess with the our data (List<Article>), otherwise we return DataFailed with DioError so the presentation will know and again if any changes occur it will not affect the presentation layer (Bloc/UI).
Note here that the DioError class provides us the error type that affected the error itself (CONNECT_TIMEOUT, RESPONSE, CANCEL, …etc) and each type describes a different message. READ MORE HERE
Remember that each bloc should solve/do one and only one problem/task. In our case we have to get a remote data then display what ever the result is in the UI (emit states to the UI). Let’s give this bloc a name which is “remote_articles”.
Create a new bloc in the presentation/blocs folder and give it the name above.
Creating a bloc can be little bit hard but you can install and use this extension provided by the same person who invented the Bloc. After that right click on the blocs folder and you should see an option called “Bloc: New Bloc” click on it then specify the name “remote_articles” and congrats your bloc has been created easily.
Now, open the remote_articles_state.dart file and change its contents to this
Those are our states (Loading, Done, Error) I think everything is clear once you read the code. Each state has its own properties and being passed to the super class RemoteArticlesState.
Head to the remote_articles_event.dart file and also change its content to this
We have only one event to send it to our bloc which is the “GetArticles”.
The interesting part is the bloc itself, so let’s open the remote_articles_bloc.dart and changes its content to this
Well, lots of stuff… let me make it clear for you and explain things in details here.. first this bloc get events of type RemoteArticlesEvent and emit states of type RemoteArticlesState and this is what we defined above (state & events).
Then, this bloc depends on the GetArticlesUseCase that we defined in the domain/usecases (will be injected later) this usecase itself should return our needs (successful data or an error). We store the incoming successful data in the variable (_articles), we have also the pagination (_page, _pageSize).. simply each time we get event and receive a successful data we increment the _page by 1 so next request the page will be 2, 3 and so on. After that we check (in the overridden method mapEventToState) if the event is “GetArticles” then we return the method _getBreakingNewsArticles.
Note here that I’m using some kind of different bloc class called BlocWithState and what this class does is giving us the ability to run process and change the current bloc state (Busy or Idle).. this is helpful if we want to make single process at a time and gives us a state about our current bloc.
And with that being said, our currently process is trying to get the data using the usecase and notice here that we’re using the callable functionality. And check if successful, emit Done state with our data.. otherwise emit Error state with the error caught.
Again notice here the property noMoreData that we have in the Done state, this indicates that we’ll no longer have more items to get.. in other meaning we’ve reached the end of the available data in the server.
How can I get this BlocWithState class?
Create new folder in the core folder and call it bloc then inside it create a file and call it bloc_with_state.dart which contains the following
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 the use of it. Hooks are a new kind of object that manages a widget life-cycles. They exist for one reason: increase the code-sharing between widgets by removing duplicates. So it’s a way of reducing code duplication and make code sharing much easier and simpler than ever. And flutter hooks provides us built-in 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 (useAnimationController) hook inside the build method. We can go deeply explaining how hooks are awesome, but the main docs provides a pretty good explanation.. if you’re still curious about it, READ MORE HERE.
Let’s create a file inside the presentation/views and call it breaking_news_view.dart which contains a pretty long piece of code
DO NOT FORGET to add this view (screen or page) to your configured app routes as the root or home (‘/’)!
OK!!! so what?? it’s just a code 😆… let’s explain things..
This view (screen or page) that extends of the HookWidget instead of StatelessWidget will have an AppBar and Body and obviously the AppBar is clear to understand it, so let’s jump into the body.
useScrollController hook in build() method
Notice here we’re using a hook called “useScrollController” inside the build method which gives us a scrollController instance every time the build method is called. And again we’re using a hook called “useEffect” function which takes a function as parameter and its job is like initState and dispose in the StatefulWidget. We’re initializing our scrollController by adding a listener to it in the useEffect function parameter, then in the return we’re disposing it (returning the dispose method).. as that simple.. that’s why hooks are awesome.
The useEffect takes another parameter called “keys” (List<Object>) and currently the list contains our instance.. which by this way we’re telling the hook to rebuild only of any changes happen to this key (instance).
Should I remind you why we need a scroll controller? It’s for pagination purposes as we already prepared it in our written Bloc.
_buildBody() and BlocBuilder
We’re explaining the entire code part by part.. so here we’ve the _buildBody() private method which returns a widget of type BlocBuilder.
What’s a BlocBuilder()? A BlocBuilder is a widget that listens to a specific bloc (provided in the BuildContext globally or locally) and any new state comes in, the bloc will rebuild its widgets. And here we’re listening to a bloc of type RemoteArticlesBloc, then we’re returning a different widget for each proper state to the builder method of the BlocBuilder. And then we’re making check through all of our possible states (Loading, Done, Error) and return the proper widget for it.
Notice here the noMoreData boolean value that comes from our bloc state, we use it here to build a circular indicator at the bottom of the list and the value is changing depending on the availability of the data in the server.
This method will be returned once we’re get a RemoteArticlesDone state in the BlocBuilder, which contains the a ListView scrollable widget that holds our data (Articles).
Notice here, we’re not using the ListView.builder to build our articles.. instead we’re using the spread operator […] which appends the given List to the current that we have. This helps us to add another widgets also by using the spread operator for the loading widget at the end of the ListView that indicates if the ListView is loading more items or not.
Obviously, this method describes itself since added it in the scrollController when we initialized it using the hook.. its job is listening to the scroll changes and detect if the user scrolled to the end of the ListView, then we add an event (GetArticles) to our bloc (provided globally in the main.dart.. will be explained later) to get us new/fresh data (Articles). And every time we do that we also increasing the page we’re in it currently until we reach the last available page and items in the server.
Notice here, we’re checking the state of our bloc and the position of the scrolling at the same time.. and if the state is not busy, then we send new network request.
You’re free to use the BlocWithState or not, but keep in mind that you shouldn’t send requests once you reach the bottom of the scroll because this will send multiple requests at the same time in a very quickly process which lead to server and app errors.
We finished from our Domain that contains (entities and abstracted classes such as repositories), we also implemented those abstraction in the Data.. then we headed to the Presentation and made our only existing view to display the entire results.
Now, we have to prepare and inject our dependencies to those classes that’s need to be injected.
Open the src/injector.dart file which contains the following
Well, here’s our injector (GetIt instance) as we defined it here globally which will hold our dependencies and also be responsible of injecting the exact type to the exact place.
Registering a dependency
Registering a dependency has a different methods and types, so for example if we want to register an instance and we want it to be the same instance every time we get it? then this is a singleton registration. But if you want to get a different instance each time you get it? this is a factory registration.
So, as we can see here we are registering the Dio instance as a singleton because our ApiNewsService depends on a Dio instance, right?
After that we’re also registering our ApiNewsService as a singleton but here this service itself depends on another dependency, so we just pass our injector and it will do the rest (inject this service with Dio instance since we’ve already registered it).
And so on, registering our repository (ArticlesRepository) as a singleton which also depends on the an ApiNewsService instance (injector will also inject this because we already registered it).
The same thing for the usecases and the blocs since all of those needs to be injected by the dependencies they need.
Well, few things here we need to do.. firstly we need to provide our bloc instance here globally (as global state) by wrapping our MaterialApp with BlocProvider and also we don’t forget to initialize our dependencies asynchronously in the main function as follows
Notice here when we creating an instance of the RemoteArticlesBloc we’re injecting it (getting an already registered instance) using the injector. Then immediately after providing that instance, we’re adding an event (GetArticles) so when we launch the app, we should have already added an event to the bloc (normally done in the initState()).
And we don’t forgot to initialize our dependencies in the void main before running the app.
Now go ahead and run the app.. and congrats your app should get its data from the server perfectly 😎.
Well done mate.. Now I can tell you that you’ve made an incredible job here and also right now I should fall in a deep sleep 😆.
Part 3 will explain and implement the local data (final part).
Consider giving the repo a pretty small star ❤️ for a little support… and see you next pal.. 😄