Basic Flutter app development with API and serialization.

Andi
7 min readAug 10, 2020

So my first year working at a startup has ended. Today I wanted to share what I’ve learned that every app that has to make API calls to a server needs.

I made an app using the api from newsapi.org and you can find the complete code on my github repository, news_app. It also has a folder with the release ready apk, and you can also find that app on Play Store.

This app uses the famous BLoC pattern, implemented using the package flutter_bloc. I have tried to keep things in the app as simple as I could while getting as much out of it as possible. And I hope it doesn’t become too confusing to read the code.

(I know the UI isn’t great. You can blame my left brain being dumb for that.)

And I could not find any sources to make this article colourful. So bear with the reading part, it will be boring but worth your time.

First comes …

How do I make API calls?

To make API calls, all you need is a package that helps you perform the basic HTTP calls. And because with the API from our source mentioned above only needs a get() call, we only need to define that.

In the context of my project, I’ve used the package Dio and created a dio_http_service.dart to perform the required get() request.

import 'package:dio/dio.dart';class DioHttpService {
Dio _dio;

DioHttpService() {
_dio = Dio(BaseOptions(connectTimeout: 10000));
}

Future<Response> handleGetRequestWithoutToken(String path) async {
String completePath = Constants.apiUrl + path;
final res = await _dio.get(completePath);
return res;
}
}

You can also make this using the http of flutter. I used Dio because if, in future you ever need to get more features for your app, Dio is a library that can help you a lot.

Now for explaining what’s happening in here: We initialized a Dio object and gave it a 10 seconds time out. You can read about the package to find out what all options it gives you to use the package for how your needs be.

Then, in the method handleGetRequestWithoutToken(), we get the path. This “path” is the URL that needs to be called, at which the API will respond. Here, we make sure that the app waits for a response using the await keyword and doesn’t just return nothing to us.

This far, we have only made a class that can go to a certain URL and get us what that URL has given us back. But we need response from the API. To keep the project organized, we will make another file for this that will handle our API calls.

Find the file news_service.dart in aforementioned repository. Because this, like the DioHttpService, is a service for app, it will be defined in the same folder services.

class NewsService {
final DioHttpService _httpService;

NewsService(DioHttpService httpService) : this._httpService = httpService;

Future<Response> fetchTopHeadlines({@required String country}) async {
String path =
"/top-headlines?country=$country&apiKey=${Constants.newsApiKey}";
final response = await _httpService.handleGetRequestWithoutToken(path);
if (response.statusCode >= 200 && response.statusCode < 300) {
return response;
} else {
throw Exception({
"statusCode": response.statusCode,
"statusMessage": response.statusMessage,
"message": "Unable to fetch levels list",
});
}
}
}

(If you’re looking at the complete code, you will find another method in there, called fetchGlobalSearchResults(). You can ignore this for now. I made it because I was bored.)

Now, what’s happening here?

  1. We defined a path variable. This is the path to give for the API call to be made. Where is this path used? Go back to our DioHttpService for that. From this path, we make a completePath in there. What we’re doing is adding the base URL and then telling the API what we need.
  2. The second line of code is the one implementing the API call. Yes, that’s it. All that to make sure this single line of code works and we have the Response.
  3. Now for the rest of the code in this function, we check the HttpStatusCode of the response. Because we performed a get request, we need a response code of 2xx to be sure we got a valid response. If that doesn’t happen, we can return an exception. Make sure you handle this during the method call.

Okay, we defined the services and we are calling them in a well-organized manner. But now where do I call this one?

(Don’t look at the project for this part. I was mistaken to be calling this from the bloc when what I should’ve done, is make a NewsRepository class that handles all the data processing for our app. This pretty much slipped off my mind and now that I revisited this project after a month, I’m too lazy to change this. My apologies for the inconvenience on this part. But follow along the rest of the article and you will have a more organized project. So please, learn from my mistake.)

We make a new class NewsRepository. This class is the bridge between the app state handler, BLoC in this context, and the API. Here, we will define the functions that the bloc will call. The called function will perform the requested API calls, and then return data obtained in a format compatible with the bloc.

class NewsRepository{ final NewsService _newsService; NewsRepository(this._newsService){}Future<ArticlesModel> getTopHeadlines(String countryCode) async {
try{
final data = await _newsService.fetchTopHeadlines(countryCode);
final models = (response.data["articles"] as List<dynamic>) .map((e) => ArticlesModel.fromMap(e));
// Here, we take only the "articles" tag because we already handled // the response errors in DioHttpService() return models;
} catch (e){
return null;
}
}
}

In this class, we get the global instance of NewsService we make at the start of the app, then using that the bloc will call this function and provide a 2-character country code. We need the country code to fetch Top Headlines for our app.

The function body is wrapped inside a try-catch block. This is because in NewsService we may return an Exception, and we now have to handle it.

As for the why the function returns this… “ArticlesModel”, or why we have the definition of this variable “models”, we now head to…

Serialization

This is the process of converting the data obtained to data objects, which are much more efficient for the purposes of storing. And with usage, you will also find them more appealing because, as in the project, it is much more convenient to get data from an object.

From the website, we know that the response we get from the API will be a json of format:

{
"status": "ok",
"totalResults": 70,
"articles": [
{
"source": {
"id": "reuters",
"name": "Reuters"
},
"author": "Nathan Frandino",
"title": "California researchers test everybody in one town for coronavirus - Reuters India",
"description": "Researchers began testing an entire town in northern California for the novel coronavirus and its antibodies on Monday, one of the first such efforts since the pandemic hit the United States.",
"url": "https://in.reuters.com/article/health-coronavirus-california-testing-idINKBN22309H",
"urlToImage": "https://s3.reutersmedia.net/resources/r/?m=02&d=20200421&t=2&i=1515823617&w=1200&r=LYNXMPEG3K05Q",
"publishedAt": "2020-04-21T06:18:01Z",
"content": "BOLINAS, Calif. (Reuters) - Researchers began testing an entire town in northern California for the novel coronavirus and its antibodies on Monday, one of the first such efforts since the pandemic hit the United States. \r\nBolinas, a wealthy beach town in Mari… [+2727 chars]"
},
},
...
]
}

It will be a very tedious task to extract all this data manually.

So what we do is:

  1. We go to https://charafau.github.io/json2builtvalue/
  2. Paste the above json, or the json you want to get from your API
  3. Remove any non-json elements, like the “…” at the end, and click “Convert”
  4. We obtain an output in which we see variables with the similar names as the keys in the json.

Remember, by default your serializer assumes that there will be no null data. If there is a variable that might get a null value, put a @nullable annonation over it. I have done it in the project and I’ll encourage you to study it.

How do we start serialization in our app?

In your pubspec.yaml, include these packages and use their latest versions:

dependencies:
flutter:
sdk: flutter
built_value: ^7.1.0
built_collection: ^4.3.2
...dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.10.0
built_value_generator: ^7.1.0

These packages are needed because we will be using some auto-generated classes needed for serializing our json data.

Now, for every unique json object that you can extract from your response, you create a new object for that. Just how in my project, I’ve done by creating SourceModel and ArticlesModel, respectively to contain the json’s “source” key and the “articles” list key.

For the next important part, take a look at our serializers.dart. You will need this file for yourself as well, and this file will be the one governing all your serialization.

part 'serializers.g.dart';@SerializersFor(const [
SourceModel,
ArticlesModel,
])
final Serializers serializers = (_$serializers.toBuilder()
..add(Iso8601DateTimeSerializer())
..addPlugin(StandardJsonPlugin()))
.build();
T deserialize<T>(dynamic value) =>serializers.deserializeWith<T>(serializers.serializerForType(T), value);BuiltList<T> deserializeListOf<T>(dynamic value) => BuiltList.from(value.map((value) => deserialize<T>(value)).toList(growable: false));

You will learn more as you explore, but what I can tell you about this code:

  1. The part files will be generated after you run a command I mention after this. So let it show error.
  2. In the annonated @SerializersFor([]), we put the classes we want serializers on.
  3. The line in which we add the Iso8601DateTimeSerializer() and the addPlugin(StandardJsonPlugin()), this is done according to our needs. The DateTimeSerializer is needed for using the DateTime objects we may get, and JsonPlugin is for performing json operations.

After your files are set up and ready, run this command:

flutter packages pub run build_runner build

This command will run and build the serializers for you. If you run into any errors, it will be mentioned in the terminal and there will, very possibly, already exist a solution for your problem.

After the command has run, you will have your app ready with the instances of ArticlesModel that our NewsRepository class will deal with.

I would also like to mention that if there are any errors in this article that I made, please be sure to correct me in the comments.

Now go ahead and use this new acquired knowledge of yours to build something beautiful.

--

--