QT Callback with result after download is completed

163 Views Asked by At

I have a QT application where several different subcomponents need to fetch data from the internet. I want to have one Network class that will do all the network interaction and then notify the caller in some way. I couldn't figure out how to do this with slots, so I coded a callback solution:

void  Network::downloadFile(QString& urlstr, const std::function<void (QString, QByteArray, QNetworkReply::NetworkError)>& callback) {
    auto processDownload = [&]() {
        QNetworkReply * reply = qobject_cast<QNetworkReply *>(sender());
        QByteArray result;
        QString filename = reply->url().fileName();
        if (reply->error() == QNetworkReply::NoError) { // Success
            result = reply->readAll();
         }
        callback(filename, result, reply->error());
        reply->deleteLater();
    };

    QNetworkReply *reply = nam_->get(QNetworkRequest(QUrl(urlstr)));
    connect(reply, &QNetworkReply::downloadProgress, this, &Network::downloadProgress);
    connect(reply, &QNetworkReply::finished, this, processDownload);
}

However I get a segfault as soon as callback is called. I tried several different ways to define the callback, and even having it empty, it still crashes (segfault mostly, sometimes bad function call). It is defined as:

std::function<void(QJsonObject, QNetworkReply::NetworkError)> f = std::bind(&MainWindow::onResult, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);

I resorted to doing something like static_cast<parent_class*>(this->parent())->onResult(..) but this is not really good decoupling. How can I do this in a way that I can pass (or signal to) any generic std::function?

EDIT: Downloadfile is usually called like this:

void MainWindow::DoNetworkRQ(QString url) {
    std::function<void(QJsonObject, QNetworkReply::NetworkError)> f = std::bind(&MainWindow::onResult, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
    network_.downloadFile(url, f);
}

Which results in a segfault. How do I do this in a QT way?

1

There are 1 best solutions below

3
JarMan On BEST ANSWER

@chehrlic was right that you are creating a callback function that goes out of scope before it gets called. Your question does not need to be solved in a "Qt way". But signals and slots will work, so I will show you how.

First, connect your signal to your slot:

void MainWindow::DoNetworkRQ(QString url) {
    connect(&network_, &Network::someSignal, this, &MainWindow::onResult);
    network_.downloadFile(url, f);
}

Then, emit the signal when your network tasks are completed:

void  Network::downloadFile(QString& urlstr, const std::function<void (QString, QByteArray, QNetworkReply::NetworkError)>& callback) {
    auto processDownload = [&]() {
        QNetworkReply * reply = qobject_cast<QNetworkReply *>(sender());
        QByteArray result;
        QString filename = reply->url().fileName();
        if (reply->error() == QNetworkReply::NoError) { // Success
            result = reply->readAll();
         }
        emit someSignal(filename, result, reply->error());
        reply->deleteLater();
    };

    QNetworkReply *reply = nam_->get(QNetworkRequest(QUrl(urlstr)));
    connect(reply, &QNetworkReply::downloadProgress, this, &Network::downloadProgress);
    connect(reply, &QNetworkReply::finished, this, processDownload);
}