How to propagate changes in SingleTon type custom QAbstractListModel on QML ListView?



  • I'm very new to QML, so having a struggle about how to propagate the changes in a custom QAbstractListModel to QML List View.

    I have the following HackNewsModel.

    The header file

    #ifndef HACKNEWSMODEL_H
    #define HACKNEWSMODEL_H
    
    #include "Singleton.hpp"
    #include <QAbstractListModel>
    #include <QJsonObject>
    #include <QDateTime>
    
    struct HackNews
    {
        QString m_id;
        bool m_deleted;
        QString m_type;
        QString m_by;
        QDateTime m_time;
        QString m_text;
        bool m_dead;
        QString m_parentId;
        QString m_pollId;
        QStringList m_kidsIdList;
        QString m_url;
        QString m_score;
        QString m_title;
        QStringList m_partsIdList;
        QString m_descendantCount;
    };
    
    class HackNewsModel : public QAbstractListModel, public Singleton<HackNewsModel>
    {
        Q_OBJECT
    
    public:
        void addHackNews(QJsonObject &hackNews);
        enum Roles {
            IdRole = Qt::UserRole + 1
        };
    
        QHash<int, QByteArray> roleNames() const override;
    
        int rowCount(const QModelIndex& parent = QModelIndex()) const override;
    
        QVariant data(const QModelIndex& index, int role/* = Qt::DisplayRole*/) const override;
    
        friend class Singleton<HackNewsModel>;
        explicit HackNewsModel(QObject * parent = nullptr);
        ~HackNewsModel() override;
    
    private:
        QList<HackNews> m_hackNewsList;
        QHash<int, QByteArray> m_roles;
    
    };
    
    #endif // HACKNEWSMODEL_H
    

    The Cpp file.

    #include "HackNewsModel.h"
    #include <QJsonArray>
    #include <QDebug>
    
    HackNewsModel::HackNewsModel(QObject *parent) : QAbstractListModel(parent)
    {
        m_roles[0] = "id";
    
        QString id = "Demo id";
        bool deleted = false;
        QString type;
        QString by;
        QDateTime time;
        QString text;
        bool dead = false;
        QString parentId;
        QString pollId;
        QStringList kidsIdList;
        QString url;
        QString score;
        QString title;
        QStringList partsIdList;
        QString descendantCount;
        m_hackNewsList.append(HackNews{id+"1", deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
        m_hackNewsList.append(HackNews{id+"2", deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
        m_hackNewsList.append(HackNews{id+"3", deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
        m_hackNewsList.append(HackNews{id+"4", deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
        m_hackNewsList.append(HackNews{id+"5", deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
    }
    
    HackNewsModel::~HackNewsModel()
    {
    
    }
    
    void HackNewsModel::addHackNews(QJsonObject &hackNews)
    {
        QString id = "Demo id";
        bool deleted = false;
        QString type;
        QString by;
        QDateTime time;
        QString text;
        bool dead = false;
        QString parentId;
        QString pollId;
        QStringList kidsIdList;
        QString url;
        QString score;
        QString title;
        QStringList partsIdList;
        QString descendantCount;
    
        if(hackNews.contains("id"))
        {
            id = hackNews["id"].toString();
        }
    
        if(hackNews.contains("deleted"))
        {
            deleted = hackNews["deleted"].toBool();
        }
    
        if(hackNews.contains("type"))
        {
            type = hackNews["type"].toString();
        }
        if(hackNews.contains("by"))
        {
            by = hackNews["by"].toString();
        }
    
        if(hackNews.contains("time"))
        {
            time = QDateTime::fromTime_t(static_cast<unsigned int>(hackNews["time"].toInt()));
        }
    
        if(hackNews.contains("text"))
        {
            text = hackNews["text"].toString();
        }
    
        if(hackNews.contains("dead"))
        {
            dead = hackNews["dead"].toBool();
        }
    
        if(hackNews.contains("parent"))
        {
            parentId = hackNews["parent"].toString();
        }
    
        if(hackNews.contains("poll"))
        {
            pollId = hackNews["poll"].toString();
        }
    
        if(hackNews.contains("kids"))
        {
            foreach (QVariant value, hackNews["kids"].toArray().toVariantList()) {
                kidsIdList.append(value.toString());
            }
        }
    
        if(hackNews.contains("url"))
        {
            url = hackNews["url"].toString();
        }
    
        if(hackNews.contains("title"))
        {
            title = hackNews["title"].toString();
        }
    
        if(hackNews.contains("parts"))
        {
            foreach (QVariant value, hackNews["parts"].toArray().toVariantList()) {
                partsIdList.append(value.toString());
            }
        }
    
        if(hackNews.contains("descendents"))
        {
            descendantCount = hackNews["descendents"].toString();
        }
    
        m_hackNewsList.append(HackNews{id, deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
    }
    
    QHash<int, QByteArray> HackNewsModel::roleNames() const
    {
        return m_roles;
    }
    
    int HackNewsModel::rowCount(const QModelIndex &parent) const
    {
        if (parent.isValid())
            return 0;
        return m_hackNewsList.size();
    }
    
    
    QVariant HackNewsModel::data(const QModelIndex &index, int /*role*/) const
    {
        //    if (!hasIndex(index.row(), index.column(), index.parent()))
        if(!index.isValid())
            return QVariant();
    
        const HackNews &news = m_hackNewsList.at(index.row());
    
        //    if(role == IdRole){
        //        qDebug() << "Seeking id";
        return news.m_id;
        //    }
    
        //    return QVariant();
    }
    

    However, this data model gets updated through NetworkRequestMaker that makes some request to a network and updates the model.

    Header file of NetworkRequestMaker.

    #ifndef NETWORKREQUESTMAKER_H
    #define NETWORKREQUESTMAKER_H
    
    #include <QObject>
    #include <QNetworkAccessManager>
    
    class QNetworkReply;
    class NetworkRequestMaker : public QObject
    {
        Q_OBJECT
    
    public:
        explicit NetworkRequestMaker(QObject *parent = nullptr);
        void startRequest(const QUrl &requestedUrl);
        void httpReadyRead();
        void httpFinished();
    
    private:
        QUrl url;
        QNetworkAccessManager m_qnam;
        QNetworkReply *m_reply;
    };
    
    #endif // NETWORKREQUESTMAKER_H
    

    Cpp file.

    #include "NetworkRequestMaker.h"
    #include <QNetworkReply>
    #include <QDebug>
    #include <QJsonDocument>
    #include <QJsonArray>
    #include <QJsonObject>
    #include <QDateTime>
    #include "HackNewsModel.h"
    
    NetworkRequestMaker::NetworkRequestMaker(QObject *parent)
        : QObject(parent),
          m_reply(nullptr)
    {
        startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/8863.json?print=pretty"));
        startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/2921983.json?print=pretty"));
        startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/121003.json?print=pretty"));
        startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/192327.json?print=pretty"));
        startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/126809.json?print=pretty"));
        startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/160705.json?print=pretty"));
    }
    
    void NetworkRequestMaker::startRequest(const QUrl &requestedUrl)
    {
        url = requestedUrl;
        m_reply = m_qnam.get(QNetworkRequest(url));
        connect(m_reply, &QNetworkReply::finished, this, &NetworkRequestMaker::httpFinished);
        connect(m_reply, &QIODevice::readyRead, this, &NetworkRequestMaker::httpReadyRead);
    }
    
    void NetworkRequestMaker::httpReadyRead()
    {
        QString strReply = QString(m_reply->readAll());
        QJsonDocument jsonResponse = QJsonDocument::fromJson(strReply.toUtf8());
        QJsonObject jsonObj = jsonResponse.object();
        HackNewsModel::getInstance().addHackNews(jsonObj);
    }
    
    void NetworkRequestMaker::httpFinished()
    {
        if (m_reply->error()) {
            qDebug()<<tr("Download failed:\n%1.").arg(m_reply->errorString());
        }
    }
    

    The singleton class is as the following.

    #ifndef SINGLETON_HPP
       #define SINGLETON_HPP
       
       template <typename T>
       class Singleton
       {
       public:
       
       	/*!*************************************************************************
       	\brief      Constructs the singleton (if necessary) and returns the pointer.
       	****************************************************************************/
       	static T& getInstance()
       	{
       		static T _singleton; //!< Unique instance of class T
       		return _singleton;
       	}
       
       protected:
       
       	/*!*************************************************************************
       	\brief      Constructor.
       	\note		protected to avoid misuses.
       	****************************************************************************/
       	Singleton() {}
       
       	/*!*************************************************************************
       	\brief      Destructor.
       	\note		protected to avoid misuses.
       	****************************************************************************/
       	virtual ~Singleton() {}
       
       	/*!*************************************************************************
       	\brief      Copy constructor.
       	\note		protected to avoid misuses.
       	****************************************************************************/
       	Singleton(const Singleton&);
       
       	/*!*************************************************************************
       	\brief      Assignment operator.
       	\note		protected to avoid misuses.
       	****************************************************************************/
       	Singleton& operator=(const Singleton&);
       };
       
       #endif // SINGLETON_HPP
    

    My QML file is as below.

    import QtQuick 2.12
    import QtQuick.Controls 2.12
    import QtQuick.Layouts 1.12
    import Hacknews 1.0
    
    Frame {
        width: 640
        height: 480
        ListView {
            id: listView
            anchors.fill: parent
            model: HackNewsModel {}
    
            delegate: Text {
                text: model.id
            }
        }
    }
    

    Despite the model being a singleton, qmnl listview doesn't show the updated entires. How do I enable it to show the updated entries?

    Thanks.


  • Qt Champions 2018

    @qml.newbie said in How to propagate changes in SingleTon type custom QAbstractListModel on QML ListView?:

    m_hackNewsList.append(HackNews{id, deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});

    You need to call beginInsertRows/endInsertRows before/after m_hackNewsList.append(HackNews{id, deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});



  • @VRonin said in How to propagate changes in SingleTon type custom QAbstractListModel on QML ListView?:

    @qml.newbie said in How to propagate changes in SingleTon type custom QAbstractListModel on QML ListView?:

    m_hackNewsList.append(HackNews{id, deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});

    You need to call beginInsertRows/endInsertRows before/after m_hackNewsList.append(HackNews{id, deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});

    I have now added these lines but even now it's not working. Could be down to something I did wrong in the main.cpp where I'm registering the model for qml? The main cpp file is as below.

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include "NetworkRequestMaker.h"
    #include "HackNewsModel.h"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
        QGuiApplication app(argc, argv);
    
        NetworkRequestMaker testRequestMaker;
    
        qmlRegisterType<HackNewsModel>("Hacknews", 1, 0, "HackNewsModel");
    
        QQmlApplicationEngine engine;
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        engine.load(url);
    
        return app.exec();
    }
    


Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.