[Qt/C++] WORKING WITH REST API

[Qt/C++] WORKING WITH REST API

WHAT ARE APIs?

API is short for Application Programming Interface. You can think of it as an intermediary between computers or software systems, It provides an environment for those systems to interact with each other, Usually you have a client(your application) which requests information from the server(service) and the API determines how that entire environment will function.

How is Data passed through APIs?

The predominant form in which data is passed in an environment leveraging APIs is through JSON which stands for JavaScript Object Notation which is a light-weight text-based format used for representing structured data based on JavaScript object syntax.

Data in JSON is represented in key - value pairs

image.png

Those data can be encapsulated in an array or object form. Data encapsulated in "curly braces" {} denote object-form while Data encapsulated in "square brackets" [] denote array-form.

image.png

NB : As you can see JSON objects can be nested into one another, the same goes for JSON arrays

REST APIs

If you've been in the software development ecosystem for even a little while, you've most likely heard of REST APIs. REST is short for Representational State Transfer which is a software architectural style. A REST API an API that follows the REST design principles. Unlike some other API models, REST APIs are flexible, user-friendly and easy to work with across multiple programming languages and it has non-strict design principles which is why it's easy to work with.You can read on REST design principles here.

Working with REST APIs

So I've chosen the Qt application framework as the tool which I will use to demonstrate how to work with REST APIs.

Qt ?

Qt at it's base is an application framework written in C++ which can be used to develop wide range of cross-platform applications and graphical user interfaces.

I've decided to use the GitHub REST API for the demonstration on working with REST APIs

First I will create a Project called GitHub_API in Qt Creator(the IDE for Qt framework) which inherits the QMainWindow base class and add the network module in our project file because we will be working with networks in this project.

image.png

image.png

I have designed a simple GUI to use with our API program using Qt Designer

image.png

NB : The goal of this article is to show how to work with REST APIs in Qt which already assumes you have basic knowledge on how Qt works

Important Qt Objects Used

  • QNetworkAccessManager - allows our application to send and receive network replies
  • QNetworkRequest - contains a request to be sent to a QNetworkAccessManager object
  • QNetworkReply - embodies a replywhich is gotten when a request is carried out by QNetworkAccessManager

We define a few variables that we will be using in our program:

image.png

  • netManager - will manage our network requests and replies
  • netReply - will handle most of the replies we get from the API
  • repoReply - will handle the replies we get concerning repositories from GitHub
  • dataBuffer - will contain the data we receive from the API
  • img - will contain the an image (profile pic)

We want this program to collect a 'GitHub Username' from the user and then it will request information on the GitHub user(if it exists) through the GitHub API, the API will return the information in form of JSON and the program will make use of that data to display some information through the GUI

image.png

When the user clicks on the USERNAME Button, it should bring up a simple dialog window in which the user would input the username of the GitHub Account

image.png

And then when the user inputs the username and clicks on the ok button, we first store that username into a variable then we call the clearValues() function which resets all the information displayed i.e It resets the program to its default state which is when no information is being displayed.

image.png

Those variables that are being cleared are the GUI elements that you see visually in the program when information on a GitHub User is being showed, we want to make sure they're empty before we display info

image.png

After they're cleared, we create two QNetworkRequest variables (req, repoReq) which we will use to request for information on the user and the repositories of the user

image.png

How do we request information from the GitHub API

Open your browser and type in api.github.com/users/YOUR_USERNAME and you'll see that it will return information on the GitHub username you put in place of YOUR_USERNAME in form of a JSON document. In my case:

image.png

So we put in api.github.com/users/%1 as the URL for req and api.github.com/users/%1/repos for repoReq.

NB : %1 is a placeholder text, it will be replaced by what is passed into the arg method, In this case it will be replaced with the username variable..It's similar to how C's printf() function works

We then call the get method for the QNetworkAccesManager object and pass in req as the argument. It basically gets the content of what the reqrequests (which in this case is the information of the user in JSON format) and returns it in form of a QNetworkReply object.

image.png

Then we store what is returned into the netReply variable and then we connect its signals(readyRead and finished) to our custom slots readData and finishedReading

  • readyRead signal is emitted when there is data to be read from the netReply object
  • finished signal is emitted when the netReply object has finished processing the data it got from the request

So basically readData and finishedReading is called when the readyRead and finished signal for the netReply object is emitted

//READ DATA CUSTOM SLOT

void MainWindow::readData()
{
    dataBuffer.append(netReply->readAll());
}

//FINISHED READING CUSTOM SLOT
void MainWindow::finishReading()
{
    if(netReply->error() != QNetworkReply::NoError){
        qDebug() << "Error : " << netReply->errorString();
        QMessageBox::warning(this,"Error",QString("Request[Error] : %1").arg(netReply->errorString()));
    }else{

        //CONVERT THE DATA FROM A JSON DOC TO A JSON OBJECT
        QJsonObject userJsonInfo = QJsonDocument::fromJson(dataBuffer).object();

        //SET USERNAME
        QString login = userJsonInfo.value("login").toString();
        ui->usernameLabel->setText(login);

        // SET DISPLAY NAME
        QString name = userJsonInfo.value("name").toString();
        ui->nameLabel->setText(name);

        //SET BIO
        auto bio = userJsonInfo.value("bio").toString();
        ui->bioEdit->setText(bio);

        //SET FOLLOWER AND FOLLOWING COUNT
        auto follower = userJsonInfo.value("followers").toInt();
        auto following = userJsonInfo.value("following").toInt();
        ui->followerBox->setValue(follower);
        ui->followingBox->setValue(following);

        //SET ACCOUNT TYPE
        QString type = userJsonInfo.value("type").toString();
        ui->typeLabel->setText(type);

        //SET PICTURE
        auto picLink = userJsonInfo.value("avatar_url").toString();
        QNetworkRequest link{QUrl(picLink)};
        netReply = netManager->get(link);
        connect(netReply,&QNetworkReply::finished,this,&MainWindow::setUserImage);
        dataBuffer.clear();
    }
}

void MainWindow::setUserImage()
{
    img->loadFromData(netReply->readAll());
    QPixmap temp = img->scaled(ui->picLabel->size());
    ui->picLabel->setPixmap(temp);
}
  • readData - appends the data(JSON document) received from the executed request into the data buffer(QByteArray object)

NB : A QByteArray is used to store raw bytes

  • finishedReading - first checks to see if an error occurred during the request, if there was an error it will display a simple dialog window notifying the user of the error. If there was no error it does the following steps :

    • Convert the bytes of data in the dataBuffer to a JSON Document and then convert that document to a JSON object named userJsonInfo, we can do this because remember when i said JSON objects are denoted using curly-braces {}, If you go to the site on your browser you will see that all the data is in fact enclosed in a curly brace which means that we can convert that document straight-away into a JSON object.
    • We then retrieve the username of the GitHub user by calling the value method on the userJsonInfo variable which expects a "key", it will return the value associated with the key (it will return a QJsonValue object which we convert to a string by calling it's toString() method), We pass in the "login" key and we expect that it would return the username value associated with it. Then we pass the value in to the usernameLabel Label object which is in our GUI.
      • We repeat the same action multiple times and get the display name, bio, number of followers and followings and the account type
      • We then request for the display picture of the user and connect the finished signal to a custom slot setUserImage then we clear our buffer because we dont need to store any more data.
    • setUserImage - gets the image from the buffer then it scales it down to the size of the picLabel object so it can fit in it and then sets the picLabel object to show the image by calling setPixmap()

image.png

NB : The dataBuffer object gets cleared after the setUserImage function is completed

So that's how the user info is retrieved from the API...Then we retrieve the repository info of that user because we haven't done that.

image.png

All the things we explained above happened in those two lines, then the next line of code executes which executes the request for repository info on the user. We connect some more custom slots to the repoReply object which will contain the JSON document that has information on the repositories of the GitHub Account.

void MainWindow::readDataForRepo()
{
    dataBuffer.append(repoReply->readAll());
}

void MainWindow::finishedGettingRepos()
{
    if(repoReply->error() != QNetworkReply::NoError){
        qDebug() << "Error Getting List of Repo: " << netReply->errorString();
        QMessageBox::warning(this,"Error",QString("Request[Error] : %1").arg(netReply->errorString()));
    }else{
        QJsonArray repoInfo = QJsonDocument::fromJson(dataBuffer).array();
        ui->repoBox->setValue(repoInfo.size());
        for(int i{0}; i < ui->repoBox->value(); ++i){
            auto repo = repoInfo.at(i).toObject();
            QString repoName = repo.value("name").toString();
            ui->repoList->addItem(repoName);
        }
    }
}

image.png

  • readDataForRepo - appends the data(JSON document) into the dataBuffer
  • finishedGettingRepos - first checks if there was an error with the request and displays the error to the user, if there was no error it creates a JSON document out of the data stored in our dataBuffer and then it converts it into an array because the information in the document is enclosed in square braces which means it's a json array. Then it sets the value of the repoBox GUI element to the size of the array(the size of the array would be the number of public repos that user has)

image.png

We then use a loop(that is constricted to the length of the array) to go through each item(repo) in the array and get its name and then add it to our list widget which is another of our GUI component

image.png

NB : A QListWidget is used to display items in a .........list form

And that's all, if we build and run the application we get the GUI i had designed and pass in my GitHub username, we get this:

image.png

Aside from the Trash UI, the program works as intended, It successfully interacts with the GitHub API!!

PS : Here is theREPO which contains this entire program in case you want to view the entire source code