Building a simple news aggregator app with Flutter
Powered by Dart, Flutter is:
Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Android in record time.
I am going to walk us through the steps to building a news aggregator app with Flutter framework.
Prerequisites
I am assuming you have already installed Flutter on your development machine and built a test Flutter project. However, if you haven’t done so, please follow any of these guilds for Linux, Mac, or Windows machines.
Now create a fresh Flutter Project and name it news_aggreator.
For the purpose of this tutorial, we’ll be using the New York Times Top Stories API. Please get your API key here (select “Top Stories” from the API drop-down).
Let’s start.
Setting up Dart
Delete all the code from lib/main.dart and replace it with the following:
The lines import ‘package:news_aggreator/home_page.dart’
and new MyHomePage(title: “News Aggregator”)
will show errors. That’s because we haven't created ahome_page.dart
file and MyHomePage
class. We’ll do that next.
Create a new dart file by navigating to File > New > Dart File. Name it home_page.
In this new file, we’ll create a MyHomePage
class that extends StatefulWidget.
A Stateful widget supports state changes and user interactions…,they maintain state that might change during the lifetime of the widget. Stateless widgets are immutable, meaning that their properties can’t change — all values are final.
— Official Docs
Import package:flutter/material.dart
and override createState()
and return a State Widget. The final code should look like this:
Now we’ll create a _MyHomePageState
class in the same home_page file. Note the “_” (underscore) before the class name? That’s Dart’s way of making an identifier private in its library. _MyHomePageState
should extend State<MyHomePage>
and override Widget build(BuildContext context) {}
. Make these changes to it:
That’s OK for now. We’ll return to it later.
Making API calls with Flutter
Now, we’re gonna make a network call to the New York Times API to get the top stories. The response looks like this:
{
...
"results": [
{
...
"title": "This is the article Title is This",
"abstract": "A summary of what this article is talking about",
"url": "https://www.THE_LINK_TO_THE_ARTICLE.html",
...
"created_date": "2018-03-30T05:00:08-04:00",
...
"multimedia": [
{...},
{...},
{...},
{
"url": "https://www.IMAGE_URL.png",
...
},
{...}
],
...
},
{...}
{...}
{...}
{...}
]
}
Add the following import statements to the top of home_page,
import 'package:http/http.dart' as http; // For network calls
import 'dart:convert'; // Amongst other functions, for decoding and encoding JSON
So, we’ll make the API calls, decode, and parse the response with this:
void _sendRequest() async {
String url = "https://api.nytimes.com/svc/topstories/v2/technology"
".json?api-key=API_KEY_HERE";
http.Response response = await http.get(url);
Map decode = json.decode(response.body);
List results = decode["results"];
for (var jsonObject in results) {
print(jsonObject);
}
}
I will explain the above lines below.
Dart is single-threaded, and so long-lasting functions such as network calls will make your app freeze. This is where the Future API comes in. A future is a way of saying:
“I want to get data from the NY Times API, but I know it might take some time, but please don’t block the thread of execution. And while you are at it, you can return an uncompleted object. Emmm… Can you also let me know when the response is ready?”
So how do we know when the response is ready? We either use callbacks or await
for the request to return. We won’t be using callbacks in this tutorial but rather the latter — aync
and await
keywords.
http.Response response = await http.get(url);
makes the actual network call.
On the surface, it looks like synchronous code but it is actually asynchronous. Read more about futures here.
Map decode = json.decode(response.body);
decodes the response body and assigns it to key-value pairs.
List results = decode[‘results’];
gets the JSON array with key “results” while
for (var jsonObject in results) {
print(jsonObject);
}
loops through the JSON array and prints each of the JSON objects to the console.
Parsing JSON responses in Flutter
The jsonObject
holds a reference to each of the JSON objects in the results
JSON array, and it looks like this:
{
...
"title": "The Article Title is This",
"abstract": "A summary of what this article is talking about",
"url": "https://www.THE_LINK_TO_THE_ARTICLE.html",
...
"created_date": "2018-03-30T05:00:08-04:00",
...
"multimedia": [
{...},
{...},
{...},
{
"url": "https://www.IMAGE_URL.png",
...
},
{...}
],
...
},
So we’re gonna parse the response and create a Post
object with it. For example, to get the title above, we’re gonna do jsonObject[‘title’]
. Yes, it’s that simple.
Create a Post
class directly under _MyHomePageState
class. The whole Post
class should look like this:
Now, create a List<Post> postList = []
field before the build method of _MyHomePageState class. Also, create a _isRequestSent
boolean field (it should default to false.
). This will hold the value of the request status.
Now, we have this:
for (var jsonObject in results) {
var post = Post.getPostFrmJSONPost(jsonObject);
postList.add(post);
print(post);
}
setState(() => _isRequestSent = true);
The setState
is a way of telling Flutter to rebuild the Widgets and redraw the UI. So you should arrive at this:
The only new line here is the new ListView.builder
. It is synonymous with Android’s RecyclerView or ListView. It calls itemBuilder: (BuildContext context, int index)
as many times as the value assigned to itemCount
. Note that a Widget must be returned in itemBuilder
.
Next, we’ll return a proper Widget in _getPostWidgets
.
Now, we’re almost done. The remaining part is the details page. There’s nothing special going on here, so I won’t dwell much on it. Create a dart file, name it post_details, and write the following:
Note the usage of the intl package for date parsing. The installation instruction can be found here. Another line worthy of mention here is:
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Generally, Global Keys are used to uniquely identify widgets. You can dive into it in-depth here: GlobalKey.
Now update openDetailsUI
with:
openDetailsUI(Post post) {
Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) => new PostDetails(post)));
}
Remember to add this import statement import ‘package:news_aggreator/post_details.dart’;
to the imports section of home_page.dart.
I guess that’s that!
In order to reduce the length of this article, I have skipped some stuff like error handling for network calls, network image caching and placeholders, and so on.
You can check the complete and working GitHub repo here:
Please feel very free to point out errors or make any corrections and suggestions.