Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Dependency injection in C++ class registered with QML_SINGLETON

Dependency injection in C++ class registered with QML_SINGLETON

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
c++qml qmlsingleton
13 Posts 6 Posters 2.2k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • T talksik

    @ECEC were you able to figure this out? I have a similar dilemma. However, I have model classes declared in c++ with the QML_ELEMENT only. I use this in my qml, but I want to pass in a “repository” or data manager class that is in c++ and would handle data fetching and a websocket connection. This data manager class has the QML_SINGLETON macro.

    On initialization of the various models in qml (ex: AuthModel {} or UserProfileModel {}) I have no way of providing it the necessary dependency of the data manager singleton. As a workaround, I can have connections in qml that manages signals and slots between the data manager singleton in qml and the models that were initialized, but it feels really off and spaghetti because qml layer shouldnt need to handle model to data layer communication. The model in c++ should be able to abstract the complexity.

    By the way, I need data manager instance to be shared instance instead of each model initializing it themselves because I only want one websocket connection.

    I find the qml and c++ integration as messy.

    Any ideas?

    T Offline
    T Offline
    talksik
    wrote on last edited by talksik
    #3

    I managed to find a way to enable the dependency injection pattern:

    // contactsModel.h (SEE the `MessengerService*` property)
    
    class ContactsModel: public QAbstractListModel
    {
        Q_OBJECT
        QML_ELEMENT
        Q_PROPERTY(MessengerService* messengerService READ messengerService WRITE setMessengerService NOTIFY messengerServiceChanged REQUIRED)
    
    public:
        enum ContactRoles
        {
            DisplayName = Qt::UserRole + 1,
            Bio,
            ProfilePictureUrl,
            Email,
            Tuned,
            Status
        };
    
        ContactsModel(QObject *parent = nullptr);
    
        int rowCount(const QModelIndex &parent = QModelIndex()) const;
        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    
        MessengerService* messengerService() const;
        void setMessengerService(MessengerService* _messengerService);
    
    signals:
        void messengerServiceChanged();
    
    public slots:
        // fetches contacts from api
        void fetchContacts();
        // update whether or not we are tuned into this person
        void updateTuneContact(const QString &email, const bool tuneIn = false);
    
    private slots:
        void finishedFetchContacts();
        void finishedUpdateTuneContact();
    
    protected:
        QHash<int, QByteArray> roleNames() const;
    
    private:
        QList<Contact> m_contacts;
    
        QNetworkAccessManager *m_net_access_manager;
        QNetworkReply *m_net_contacts_reply;
        QNetworkReply *m_net_update_tuned_contact;
    
        MessengerService* m_messenger_service;
    };
    
    // messengerservice.h (This is what I want as a shared dependency between different models)
    // in charge of managing connection with messenger service
    // realtime & presence messages
    class MessengerService: public QObject
    {
        Q_OBJECT
        QML_ELEMENT
        QML_SINGLETON
    public:
        MessengerService() = default;
    
    signals:
        // filter for presence only messages
        // these would be pongs of presence heartbeats to other clients
        void incomingPresencePong(const IncomingPresencePong &message);
        void incomingPresenceQuery(const IncomingPresenceQuery &message);
    
        // all messages
        void messageReceived(const QString &message);
    
    public slots:
        void connect(const QString &email);
    
        // successfully connected
        void connected();
        void disconnected();
        void errorOccurred(QAbstractSocket::SocketError err);
    
        void sendTextMessage(const QString &message);
    
        void sendPresencePong(const OutgoingPresencePong &pong);
        void sendPresenceQuery(const OutgoingPresenceQuery &query);
    
    private slots:
        void textMessageReceived(const QString &message);
    
    private:
        QWebSocket m_webSocket;
    };
    }
    
    // Now in our QML
        ContactsModel {
            id: contactsModel
            messengerService: MessengerService
        }
    

    Now, the compiler will require the ContactsModel initialization to include an instance of MessengerService. Since MessengerService is registered as a QML_SINGLETON, it can be passed in.

    We can then also pass the same MessengerService singleton to other models that are initialized in QML.

    @SGaist any thoughts on this approach of mine? My experience with golang backend design patterns and also flutter bloc pattern inspired me to do it this way.

    Do you know if there is an enterprise standard patterns for Qt/QML apps? I am shocked that Qt doesn't document how complex apps should be written (where there are many "view models" needing to access each other.

    B 1 Reply Last reply
    1
    • T talksik

      I managed to find a way to enable the dependency injection pattern:

      // contactsModel.h (SEE the `MessengerService*` property)
      
      class ContactsModel: public QAbstractListModel
      {
          Q_OBJECT
          QML_ELEMENT
          Q_PROPERTY(MessengerService* messengerService READ messengerService WRITE setMessengerService NOTIFY messengerServiceChanged REQUIRED)
      
      public:
          enum ContactRoles
          {
              DisplayName = Qt::UserRole + 1,
              Bio,
              ProfilePictureUrl,
              Email,
              Tuned,
              Status
          };
      
          ContactsModel(QObject *parent = nullptr);
      
          int rowCount(const QModelIndex &parent = QModelIndex()) const;
          QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
      
          MessengerService* messengerService() const;
          void setMessengerService(MessengerService* _messengerService);
      
      signals:
          void messengerServiceChanged();
      
      public slots:
          // fetches contacts from api
          void fetchContacts();
          // update whether or not we are tuned into this person
          void updateTuneContact(const QString &email, const bool tuneIn = false);
      
      private slots:
          void finishedFetchContacts();
          void finishedUpdateTuneContact();
      
      protected:
          QHash<int, QByteArray> roleNames() const;
      
      private:
          QList<Contact> m_contacts;
      
          QNetworkAccessManager *m_net_access_manager;
          QNetworkReply *m_net_contacts_reply;
          QNetworkReply *m_net_update_tuned_contact;
      
          MessengerService* m_messenger_service;
      };
      
      // messengerservice.h (This is what I want as a shared dependency between different models)
      // in charge of managing connection with messenger service
      // realtime & presence messages
      class MessengerService: public QObject
      {
          Q_OBJECT
          QML_ELEMENT
          QML_SINGLETON
      public:
          MessengerService() = default;
      
      signals:
          // filter for presence only messages
          // these would be pongs of presence heartbeats to other clients
          void incomingPresencePong(const IncomingPresencePong &message);
          void incomingPresenceQuery(const IncomingPresenceQuery &message);
      
          // all messages
          void messageReceived(const QString &message);
      
      public slots:
          void connect(const QString &email);
      
          // successfully connected
          void connected();
          void disconnected();
          void errorOccurred(QAbstractSocket::SocketError err);
      
          void sendTextMessage(const QString &message);
      
          void sendPresencePong(const OutgoingPresencePong &pong);
          void sendPresenceQuery(const OutgoingPresenceQuery &query);
      
      private slots:
          void textMessageReceived(const QString &message);
      
      private:
          QWebSocket m_webSocket;
      };
      }
      
      // Now in our QML
          ContactsModel {
              id: contactsModel
              messengerService: MessengerService
          }
      

      Now, the compiler will require the ContactsModel initialization to include an instance of MessengerService. Since MessengerService is registered as a QML_SINGLETON, it can be passed in.

      We can then also pass the same MessengerService singleton to other models that are initialized in QML.

      @SGaist any thoughts on this approach of mine? My experience with golang backend design patterns and also flutter bloc pattern inspired me to do it this way.

      Do you know if there is an enterprise standard patterns for Qt/QML apps? I am shocked that Qt doesn't document how complex apps should be written (where there are many "view models" needing to access each other.

      B Offline
      B Offline
      Bob64
      wrote on last edited by
      #4

      @talksik I had a similar reaction to the advice to move away from context properties to singletons. However, it was pointed out to me that it wasn't necessary for the C++ object actually to be a singleton.

      The advice was to use qmlRegisterSingletonInstance, whereby an object is registered as the instance that is accessed from QML. The object just needs to be a QObject and provide a QML-compatible API, but how it is created, what constructor arguments it has, etc., is up to you. The "singleton" aspect simply refers to the syntax with which it is accessed from QML.

      T 1 Reply Last reply
      1
      • B Bob64

        @talksik I had a similar reaction to the advice to move away from context properties to singletons. However, it was pointed out to me that it wasn't necessary for the C++ object actually to be a singleton.

        The advice was to use qmlRegisterSingletonInstance, whereby an object is registered as the instance that is accessed from QML. The object just needs to be a QObject and provide a QML-compatible API, but how it is created, what constructor arguments it has, etc., is up to you. The "singleton" aspect simply refers to the syntax with which it is accessed from QML.

        T Offline
        T Offline
        talksik
        wrote on last edited by talksik
        #5

        @Bob64 Thank you Bob for the suggestion!

        I just want to clarify something. In the qml code I provided I am initializing ContactsModel {}. However, what is not shown is that this model is initialized is a "sub-qml" file called Dashboard.qml. My main qml file looks like this:

        ApplicationWindow {
            id: root
            visible: true
        
            ...
        
            // Container element for rotating screen
            Rectangle {
                id: main
                ...
                StackView {
                    id: stackView
                    anchors.fill: parent
                    initialItem: loginView
        
                    Component {
                        id: loginView
                        Login {}
                    }
                    Component {
                        id: dashboardView
                        Dashboard {}
                    }
                }
            }
        }
        

        Note the dashboardView component. I have it this way because I do not want ContactsModel to be a top-level model. I want it to have the lifecycle of the dashboardView.

        From what you said, I imagine you are suggesting something similar to the following?

        // main.cpp
        MessengerService* messengerService = new MessengerService();
        
        ContactsModel* contactsModel = new ContactsModel(messengerService);
        
        qmlRegisterSingletonInstance(contactsModel, ...);
        

        What I am trying to achieve is the following: whenever we want to use certain models in any qml file, it should receive the MessengerService instance on initialization. Right now I am doing a hacky way by making messengerService a Q_PROPERTY that gets set in the QML where I initialize one ContactsModel {}.

        Thank you! Please let me know if that makes sense. I would appreciate any tips :)

        B 1 Reply Last reply
        0
        • T talksik

          @Bob64 Thank you Bob for the suggestion!

          I just want to clarify something. In the qml code I provided I am initializing ContactsModel {}. However, what is not shown is that this model is initialized is a "sub-qml" file called Dashboard.qml. My main qml file looks like this:

          ApplicationWindow {
              id: root
              visible: true
          
              ...
          
              // Container element for rotating screen
              Rectangle {
                  id: main
                  ...
                  StackView {
                      id: stackView
                      anchors.fill: parent
                      initialItem: loginView
          
                      Component {
                          id: loginView
                          Login {}
                      }
                      Component {
                          id: dashboardView
                          Dashboard {}
                      }
                  }
              }
          }
          

          Note the dashboardView component. I have it this way because I do not want ContactsModel to be a top-level model. I want it to have the lifecycle of the dashboardView.

          From what you said, I imagine you are suggesting something similar to the following?

          // main.cpp
          MessengerService* messengerService = new MessengerService();
          
          ContactsModel* contactsModel = new ContactsModel(messengerService);
          
          qmlRegisterSingletonInstance(contactsModel, ...);
          

          What I am trying to achieve is the following: whenever we want to use certain models in any qml file, it should receive the MessengerService instance on initialization. Right now I am doing a hacky way by making messengerService a Q_PROPERTY that gets set in the QML where I initialize one ContactsModel {}.

          Thank you! Please let me know if that makes sense. I would appreciate any tips :)

          B Offline
          B Offline
          Bob64
          wrote on last edited by
          #6

          @talksik OK, I see what you are saying. I think what you are doing is probably reasonable in that case, but somebody more expert than me might be able to make other suggestions.

          T 1 Reply Last reply
          1
          • B Bob64

            @talksik OK, I see what you are saying. I think what you are doing is probably reasonable in that case, but somebody more expert than me might be able to make other suggestions.

            T Offline
            T Offline
            talksik
            wrote on last edited by
            #7

            @Bob64 thank you!

            @SGaist Any thoughts? Appreciate you!

            SGaistS 1 Reply Last reply
            0
            • T talksik

              @Bob64 thank you!

              @SGaist Any thoughts? Appreciate you!

              SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on last edited by
              #8

              @talksik Something is not exactly clear from your explanations.

              Can you explain your architecture ? You seem to have several models that are each in charge of a different aspect of your MessengerService, is that correct ?

              If so, it seems that you should basically create all your models passing the service to them and then set them as qmlRegisterSingletonInstance as suggested by @Bob64. I would no see any advantages creating the MessengerService in QML to pass it to your model unless you plan to be able to switch MessengerService objects on the fly. This would require for your models to have a suitable API to do the change.

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              T 1 Reply Last reply
              0
              • SGaistS SGaist

                @talksik Something is not exactly clear from your explanations.

                Can you explain your architecture ? You seem to have several models that are each in charge of a different aspect of your MessengerService, is that correct ?

                If so, it seems that you should basically create all your models passing the service to them and then set them as qmlRegisterSingletonInstance as suggested by @Bob64. I would no see any advantages creating the MessengerService in QML to pass it to your model unless you plan to be able to switch MessengerService objects on the fly. This would require for your models to have a suitable API to do the change.

                T Offline
                T Offline
                talksik
                wrote on last edited by talksik
                #9

                @SGaist Thank you so much for the response!

                I considered that approach as @Bob64 suggested, but the thing is: I initialize the model within a sub-qml file deep within the QML tree. I also do NOT do the qmlRegisterSingletonInstance approach because I want the model to have the same lifecycle as the QML component that it is initialized in. Even more, this approach allow multiple of the same model being created within the QML in different places/pages/views.

                The qmlRegisterSingletonInstance registers a singleton, which is not what I would like in my architecture. I may do that if that's the only way, however, I wanted to see if there was a way to do it my way so that each model is isolated to the QML component (and sub-components) that uses it.

                You are right that I do not want to switch MessengerService objects on the fly. I am stuck in this dilemma. Please see my response to @Bob64's suggestion here for clarity: https://forum.qt.io/post/792823

                T 1 Reply Last reply
                0
                • T talksik

                  @SGaist Thank you so much for the response!

                  I considered that approach as @Bob64 suggested, but the thing is: I initialize the model within a sub-qml file deep within the QML tree. I also do NOT do the qmlRegisterSingletonInstance approach because I want the model to have the same lifecycle as the QML component that it is initialized in. Even more, this approach allow multiple of the same model being created within the QML in different places/pages/views.

                  The qmlRegisterSingletonInstance registers a singleton, which is not what I would like in my architecture. I may do that if that's the only way, however, I wanted to see if there was a way to do it my way so that each model is isolated to the QML component (and sub-components) that uses it.

                  You are right that I do not want to switch MessengerService objects on the fly. I am stuck in this dilemma. Please see my response to @Bob64's suggestion here for clarity: https://forum.qt.io/post/792823

                  T Offline
                  T Offline
                  talksik
                  wrote on last edited by
                  #10

                  @sierdzio Any ideas on this? Thank you!!! :)

                  T 1 Reply Last reply
                  0
                  • T talksik

                    @sierdzio Any ideas on this? Thank you!!! :)

                    T Offline
                    T Offline
                    talksik
                    wrote on last edited by talksik
                    #11

                    @KH-219Design I found your great details of using MVVM with Qt/QML! I agree with it completely (https://forum.qt.io/topic/127714/qt-qml-c-hybrid-application-best-practices/10) Can you please help me in the dilemma I present in this thread?

                    What if we have a ViewModel that we define in C++, but that view model and other view models want to share a data object that is a singleton. How would you pass that singleton. I instantiate my view models that were defined in C++ within my QML deep within the QML tree. I see that you don't have this problem because you send the view models to QML using context properties. Please read the comments before this to understand what I am saying.

                    I would really appreciate this as I am shocked that I still haven't found an elegant way around this :)

                    E 1 Reply Last reply
                    0
                    • T talksik

                      @KH-219Design I found your great details of using MVVM with Qt/QML! I agree with it completely (https://forum.qt.io/topic/127714/qt-qml-c-hybrid-application-best-practices/10) Can you please help me in the dilemma I present in this thread?

                      What if we have a ViewModel that we define in C++, but that view model and other view models want to share a data object that is a singleton. How would you pass that singleton. I instantiate my view models that were defined in C++ within my QML deep within the QML tree. I see that you don't have this problem because you send the view models to QML using context properties. Please read the comments before this to understand what I am saying.

                      I would really appreciate this as I am shocked that I still haven't found an elegant way around this :)

                      E Offline
                      E Offline
                      eternal_void
                      wrote on last edited by
                      #12

                      @talksik Have you found an elegant way around this? I'm struggling to find a pretty way of sharing single data source object among multiple models in QML.

                      This object communicates with QSerialPort so I guess it can't be anything but a singleton. Currently I'm using qmlRegisterSingletonInstance for all of my models which I create in main.cpp passing the data source object pointer to their constructors. It looks sort of messy if you ask me.

                      So, any ideas? Thank you!

                      1 Reply Last reply
                      0
                      • A Offline
                        A Offline
                        ajpat
                        wrote on last edited by
                        #13

                        Hey @eternal_void , I just got back to using Qt for another project. This is new account (I was @talksik), but here are my findings in a similar post after a ton of research and coming up with a custom solution not really mentioned anywhere: https://forum.qt.io/post/821291

                        1 Reply Last reply
                        1
                        • B Bob64 referenced this topic on

                        • Login

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • Users
                        • Groups
                        • Search
                        • Get Qt Extensions
                        • Unsolved