Qt Network and JSON example: a simple Hacker News reader

This tutorial will show you how to use Qt Network and JSON to create a simple Hacker News reader based on the Hacker News API. In particular we will be using high level classes of Qt Network to handle HTTP requests. Full project and source code are provided.

Qt and JSON

Please notice that this tutorial is divided over 2 pages because of its length.

A simple Hacker News reader

The example application we are going to create in this tutorial is a very simple reader which shows the 10 most recent stories posted on Hacker News.

Qt Network and JSON example: a simpleHacker News reader

I tried to keep things as simple as possible to focus on Qt Network and JSON. For this reason the reader only shows the latest 10 stories (no scrolling) and it only shows some information about each story (like title and score). It is possible to visit a story’s link clicking on its VIEW button. That will open the link in your default browser.

For the same reason, I won’t be discussing the code defining the GUI of this simple Qt application. You can always read the source code for that and ask me a questions if something is not clear.

If you need help to start with Qt you can check out my previous post about how to create an application with Qt and C++.

Qt Network

Qt Network provides a set of APIs for programming network applications that use TCP/IP.

It offers high level classes to perform normal network operations based on common protocols like HTTP and FTP, but also low level classes to handle TCP and UDP connections. The latter can be used to implement other network protocols, but also to create any kind of client/server architecture.

To use Qt Network in a Qt project all you need to do is adding “network” to the QT configuration variable in the .pro file:

QT    += core gui network

Obviously you will need to include the right headers when needed in your source code. For this application I added the following headers in MainWindow.cpp:

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>

JSON and Qt

JSON (JavaScript Object Notation) is a lightweight data-interchange text format.

Qt provides support for dealing with JSON data in its core module, so you won’t need to enable any extra module to use it. All you need to do to use JSON in your Qt project is including the headers corresponding to the JSON objects you’re going to use like the ones I added in MainWindow.cpp:

#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>

Hacker News

Hacker News is a news aggregator website focused on tech and startup news.

I decided to use their REST API for this example because it’s extremely simple and it doesn’t require any authentication. I am also a member of the community myself and I thought that creating a desktop reader would be cool, but those were secondary reasons.

Initialising the Network objects

Some important network objects are initialised in the initialisation list of the MainWindow:

MainWindow::MainWindow(QWidget * parent)
	: QMainWindow(parent)
	, mPanelStories(nullptr)
	, mNetMan(new QNetworkAccessManager(this))
	, mNetReply(nullptr)
	, mDataBuffer(new QByteArray)
{
	// ...GUI CODE...
}

The first one is mNetMan which is a QNetworkAccessManager or the object which allows the application to send network requests and receive replies like mNetReply (more on it later).

Another object which is not part of the Qt Network module, but which will be used to handle the data received, is mDataBuffer which is a QByteArray.  A QByteArray is a pumped-up version of a normal C array.

You’ll find out more about these objects in the next sections.

Requesting the list of latest stories

When the application starts the main window is pretty much empty.

a simple Hacker News reader showing no stories yet

Clicking the button REFRESH starts the data retrieving process.

The Hacker News API gives access to a list of IDs of the latest 500 stories simply downloading the following JSON file:

https://hacker-news.firebaseio.com/v0/newstories.json

That is handled by the following function:

void MainWindow::OnRefreshClicked()
{
	mButtonRefresh->setEnabled(false);

The first line simply disables the REFRESH button to avoid any problem with multiple requests.

Then we start to download the list:

	// -- DOWNLOAD LIST OF STORIES --
	const QUrl STORIES_LIST_URL("https://hacker-news.firebaseio.com/v0/newstories.json");
	mNetReply = mNetMan->get(QNetworkRequest(STORIES_LIST_URL));

	connect(mNetReply, &QIODevice::readyRead, this, &MainWindow::OnDataReadyToRead);
	connect(mNetReply, &QNetworkReply::finished, this, &MainWindow::OnListReadFinished);
}

All we need to do to download a file from the web is to create a QNetworkRequest from the URL of such file and then to submit it to the QNetworkAccessManager object with the function get. URLs in Qt Network are wrapped by QUrl objects.

The function get of QNetworkAccessManager returns a QNetworkReply object which we can use to handle the response from the server. To do that we need to connect its signals to some slots like in the last 2 lines of code.

The first connection is between the signal QIODevice::readyRead and the slot OnDataReadyToRead which basically reads the data received by the network request:

void MainWindow::OnDataReadyToRead()
{
	mDataBuffer->append(mNetReply->readAll());
}

Received data is read from the QNetworkReply using the function readAll and then stored in a QByteArray buffer using append. We read data as soon as it’s available because that is more efficient in terms of memory than reading everything at the end.

The second connection is between the signal QNetworkReply::finished and the slot OnListReadFinished which will be described in detail in the next section.

It’s worth to mention that this simple code does not handle connection errors. In a real application you might want to check other signals like QNetworkReply::error and QNetworkReply::sslErrors.

Handling the list of latest stories

Once QNetworkReply has finished receiving data it emits a QNetworkReply::finished signal which, in this program, triggers the execution of OnListReadFinished. In this function we can process the data received using JSON as showed in the following code:

void MainWindow::OnListReadFinished()
{
	QJsonDocument doc = QJsonDocument::fromJson(*mDataBuffer);
	QJsonArray array = doc.array();

	// -- STORE LATEST 10 STORIES ID --
	for(int i = 0; i < NUM_STORIES_SHOWED; ++i)
		mLatestStoriesID[i] = array[i].toInt();

The first line creates a QJsonDocument from the data in the QByteArray using the static function QJsonDocument::fromJson. We can do that because we know that the data contained in the QByteArray is an actual JSON document. We also know that the document returned by the Hacker News API contains an array, so we can access it using the function array as showed in the second line.

Once we have a JSON array we can access its values as it was a standard array using the operator []. Finally we can convert those values to integers with the function toInt and store them in the int array mLatestStoriesID to use them later.

The remaining code of the function clears the objects used to handle the network request, then creates a widget to contain the stories and finally starts downloading them using the Hacker News API.

	// -- CLEAN UP --
	NetworkCleanup();

	// -- SET UP PANEL STORIES --
	delete mPanelStories;

	mPanelStories = new QWidget;
	centralWidget()->layout()->addWidget(mPanelStories);

	QVBoxLayout * layout = new QVBoxLayout;
	mPanelStories->setLayout(layout);

	// -- START READING STORIES --
	mCurrStory = 0;

	ReadStory();
}

The function that clears the network data is pretty simple, but it introduces some interesting concepts:

void MainWindow::NetworkCleanup()
{
	mNetReply->deleteLater();
	mNetReply = nullptr;

	mDataBuffer->clear();
}

Normally QNetworkReply objects are deleted by the QNetworkAccessManager which creates them. That doesn’t happen until the QNetworkAccessManager object is destroyed, which means it’s better to get rid of them as soon as possible to free resources. It’s important to know that these objects can’t be simply deleted in the slots handling the QNetworkReply::finished signal (as they generated it).  We can schedule the destruction for the next application cycle using the function deleteLater as showed in the first line.

CONTINUE TO THE SECOND PAGE >>

Leave a Comment

Your email address will not be published. Required fields are marked *