Qt Network and JSON example: a simple Hacker News reader

<< GO BACK TO THE FIRST PAGE

Reading the latest stories

The Hacker News API provides all the data of a story simply downloading the corresponding JSON file:

https://hacker-news.firebaseio.com/v0/item/STORY_ID.json

The following function starts the request for a story in a similar way as seen before for the list of stories:

void MainWindow::ReadStory()
{
	const QString STR = QString("https://hacker-news.firebaseio.com/v0/item/%1.json").arg(mLatestStoriesID[mCurrStory++]);
	const QUrl STORY_URL(STR);

	mNetReply = mNetMan->get(QNetworkRequest(STORY_URL));

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

As seen before, we create a network request and we pass it to the network access manager to get a reply from the server.

The only differences here are the API URL and the function executed when the download is finished, which is the following:

void MainWindow::OnStoryReadFinished()
{
	QJsonDocument doc = QJsonDocument::fromJson(*mDataBuffer);

	// -- ADD STORY --
	StoryEntry * entry = new StoryEntry(mCurrStory, doc.object());
	mPanelStories->layout()->addWidget(entry);

Once the story has been downloaded we can create a JSON document as we did before for the list of stories. This document will be processed by a StoryEntry object, a custom QWidget which visualises a story in our application.

After the story has been processed it’s possible to clean up the network data and then move to the next one or end the process and re-enable the REFRESH button.

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

	// -- NEXT STORY OR FINISH --
	if(mCurrStory < NUM_STORIES_SHOWED)
		ReadStory();
	else
		mButtonRefresh->setEnabled(true);
}

In a real application you probably want to send multiple requests to read different stories asynchronously and you want to visualize them after they are all downloaded. In order to keep things as simple as possible in this example application I decided to process one story per time and to show it as soon as it was downloaded. That also gives some sense of progression which replaces a loading bar or anything like that.

Showing a story

As mentioned in the previous section a story is represented by a StoryEntry object which is defined by the following code:

StoryEntry::StoryEntry(int index, const QJsonObject & obj, QWidget * parent) : QWidget(parent)
{
	// -- NEWS URL --
	QJsonObject::const_iterator it = obj.constFind("url");

	if(it != obj.constEnd())
		mURL.setUrl(it.value().toString());
	else
		mURL.setUrl(QString("https://news.ycombinator.com/item?id=%1").arg(obj["id"].toInt()));

Not all the stories have an associated URL and for that reason we have to check if the JSON object contains one with the function constFind. If they don’t, we are going to use the URL of the story on Hacker News.

The remaining code of the constructor defines the elements in the widget:

	// -- LAYOUT --
	QHBoxLayout * layout = new QHBoxLayout;
	setLayout(layout);

	// -- INDEX --
	QLabel * label = new QLabel(QString("%1.").arg(index, 2));
	layout->addWidget(label);

	// -- TITLE --
	QFont fontTitle;
	fontTitle.setPointSize(10);
	fontTitle.setBold(true);

	label = new QLabel(obj["title"].toString());
	label->setFont(fontTitle);
	layout->addWidget(label);

	// -- HOST --
	label = new QLabel(QString("(%1)").arg(mURL.host()));
	layout->addWidget(label);

	// -- SPACER --
	layout->addSpacerItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum));

	// -- POINTS --
	const int POINTS = obj["score"].toInt();

	if(1 == POINTS)
		label = new QLabel("1 point");
	else
		label = new QLabel(QString("%1 points").arg(POINTS));

	layout->addWidget(label);

	// -- SPACER --
	layout->addSpacerItem(new QSpacerItem(20, 10, QSizePolicy::Fixed, QSizePolicy::Minimum));

	// -- VIEW BUTTON --
	QPushButton * button = new QPushButton("VIEW");
	button->setFixedWidth(100);
	layout->addWidget(button);

	connect(button, &QPushButton::clicked, this, &StoryEntry::OnViewClicked);
}

It’s important to notice how, in lines 14 and 26, the JSON object is accessed as if it was an associative map (passing a key gives you the corresponding value).

The last line of the constructor connects the QPushButton::clicked signal of the VIEW button to the slot OnViewClicked, which is quite simple:

void StoryEntry::OnViewClicked()
{
	QDesktopServices::openUrl(mURL);
}

As you might imagine, all it does is opening the URL of the story in the default browser.

Source code

The full source code of this tutorial is available on GitHub and released under the Unlicense license.

References

You might want to check out the following resources to learn more about Qt Network and JSON, but also the Hacker News API:

A real Hacker News reader

While working on this example I realised that a proper Hacker News reader would be an interesting project to work on and I might decide to go for it if enough people are interested.

Would you use it on your desktop computer (Linux, OS X and/or Windows)?

If yes, which features would you like to see in the first version?

Please let me know leaving a comment below.

Conclusion

I hope you enjoyed this tutorial explaining how to use Qt Network and JSON to create a simple Hacker News reader. If you have any question feel free to leave a comment.

If you found it useful, please share it on social media using the social buttons below.

Subscribe

Don’t forget to subscribe to the blog newsletter to get notified of future posts (once a week).

You can also get updates following me on Google+, LinkedIn and Twitter.

<< GO BACK TO THE FIRST PAGE

Leave a Comment

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