aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/doc/src/qmlsingletons.qdoc
blob: ad441eca85efacd3dc2dccdf75325496348af037 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only

/*!
\page qml-singleton.html
\title Singletons in QML
\brief A guide for using singletons in QML

In QML, a singleton is an object which is created at most once per
\l{QQmlEngine}{engine}. In this guide, we'll
\l{How can singletons be created in QML}{explain how to create} singletons
and \l{Accessing singletons}{how to use them}. We'll also provide some
best practices for working with singletons.

\section1 How can singletons be created in QML?

There are two separate ways of creating singletons in QML. You can either define
the singleton in a QML file, or register it from C++.

\section2 Defining singletons in QML
To define a singleton in QML, you first have to add
\code
pragma singleton
\endcode
to the top of your file.
There's one more step: You will need to add an entry to the QML module's
\l{Module Definition qmldir Files}{qmldir file}.

\section3 Using qt_add_qml_module (CMake)
When using CMake, the qmldir is automatically created by \l{qt_add_qml_module}.
To indicate that the QML file should be turned into a singleton, you need to set
the \c{QT_QML_SINGLETON_TYPE}
file property on it:
\code
set_source_files_properties(MySingleton.qml
    PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
\endcode

You can pass multiple files at once to \c{set_source_files_properties}:
\code
set(plain_qml_files
    MyItem1.qml
    MyItem2.qml
    FancyButton.qml
)
set(qml_singletons
    MySingleton.qml
    MyOtherSingleton.qml
)
set_source_files_properties(${qml_singletons}
    PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
qt_add_qml_module(myapp
    URI MyModule
    QML_FILES ${plain_qml_files} ${qml_singletons}
)
\endcode

\note set_source_files_properties needs to be called before \c{qt_add_qml_module}

\section3 Without qt_add_qml_module
If you aren't using \c{qt_add_qml_module}, you'll need to manually create a
\l{Module Definition qmldir Files}{qmldir file}.
There, you'll need to mark your singletons accordingly:
\code
module MyModule
singleton MySingleton 1.0 MySingleton.qml
singleton MyOtherSingleton 1.0 MyOtherSingleton.qml
\endcode
See also \l{Object Type Declaration} for more details.


\section2 Defining singletons in C++

There are multiple ways of exposing singletons to QML from C++. The main
difference depends on whether a new instance of a class should be created when
needed by the QML engine; or if some existing object needs to be exposed to a
QML program.

\section3 Registering a class to provide singletons

The simplest way of defining a singleton is to have a default-constructible
class, which derives from QObject and mark it with the \l{QML_SINGLETON} and
\l{QML_ELEMENT} macros.
\code
class MySingleton : public QObject
{
    Q_OBJECT
    QML_SINGLETON
    QML_ELEMENT
public:
    MySingleton(QObject *parent = nullptr) : QObject(parent) {
        // ...
    }
};
\endcode
This will register the \c{MySingleton} class under the name \c{MySingleton} in
the QML module to which the file belongs.
If you want to expose it under a different name, you can use \l{QML_NAMED_ELEMENT}
instead.

If the class can't be made default-constructible, or if you need access to
the \l{QQmlEngine} in which the singleton is instantiated, it is possible to
use a static create function instead. It must have the signature
\c{MySingleton *create(QQmlEngine *, QJSEngine *)}, where \c{MySingleton} is
the type of the class that gets registered.
\code
class MyNonDefaultConstructibleSingleton : public QObject
{
    Q_OBJECT
    QML_SINGLETON
    QML_NAMED_ELEMENT(MySingleton)
public:
    MyNonDefaultConstructibleSingleton(QJSValue id, QObject *parent = nullptr)
        : QObject(parent)
        , m_symbol(std::move(id))
    {}

    static MyNonDefaultConstructibleSingleton *create(QQmlEngine *qmlEngine, QJSEngine *)
    {
         return new MyNonDefaultConstructibleSingleton(qmlEngine->newSymbol(u"MySingleton"_s));
    }

private:
    QJSValue m_symbol;
};
\endcode

\note The create function takes both a \l{QJSEngine} and a \l{QQmlEngine} parameter. That is
for historical reasons. They both point to the same object which is in fact a QQmlEngine.

\section3 Exposing an existing object as a singleton

Sometimes, you have an existing object that might have been created via
some third-party API. Often, the right choice in this case is to have one
singleton, which exposes those objects as its properties (see
\l{Grouping together related data}).
But if that is not the case, for example because there is only a single object that needs
to be exposed, use the following approach to expose an instance of type
\c{MySingleton} to the engine.
We first expose the Singleton as a \l{QML_FOREIGN}{foreign type}:
\code
struct SingletonForeign
{
    Q_GADGET
    QML_FOREIGN(MySingleton)
    QML_SINGLETON
    QML_NAMED_ELEMENT(MySingleton)
public:

    inline static MySingleton *s_singletonInstance = nullptr;

    static MySingleton *create(QQmlEngine *, QJSEngine *engine)
    {
        // The instance has to exist before it is used. We cannot replace it.
        Q_ASSERT(s_singletonInstance);

        // The engine has to have the same thread affinity as the singleton.
        Q_ASSERT(engine->thread() == s_singletonInstance->thread());

        // There can only be one engine accessing the singleton.
        if (s_engine)
            Q_ASSERT(engine == s_engine);
        else
            s_engine = engine;

        // Explicitly specify C++ ownership so that the engine doesn't delete
        // the instance.
        QJSEngine::setObjectOwnership(s_singletonInstance,
                                      QJSEngine::CppOwnership);
        return s_singletonInstance;
    }

private:
    inline static QJSEngine *s_engine = nullptr;
};
\endcode
Then we set \c{SingletonForeign::s_singletonInstance} before we start
the first engine
\code
SingletonForeign::s_singletonInstance = getSingletonInstance();
QQmlApplicationEngine engine;
engine.loadFromModule("MyModule", "Main");
\endcode

\note It can be very tempting to simply use \l{qmlRegisterSingletonInstance} in
this case. However, be wary of the pitfalls of imperative type registration
listed in the next section.

\section3 Imperative type registration
Before Qt 5.15, all types, including singletons were registered via the
\c{qmlRegisterType} API. Singletons specifically were registered via either
\l{qmlRegisterSingletonType} or \l{qmlRegisterSingletonInstance}. Besides the
minor annoyance of having to repeat the module name for each type and the forced
decoupling of the class declaration and its registration, the major problem with
that approach was that it is tooling unfriendly: It was not statically possible
to extract all the necessary information about the types of a module at compile
time. The declarative registration solved this issue.

\note There is one remaining use case for the imperative \c{qmlRegisterType} API:
It is a way to expose a singleton of non-QObject type as a \c{var} property via
\l{qmlRegisterSingletonType}{the QJSValue based \c{qmlRegisterSingletonType} overload}
. Prefer the alternative: Expose that value as the property of a (\c{QObject}) based
singleton, so that type information will be available.

\section2 Accessing singletons
Singletons can be accessed both from QML as well as from C++. In QML, you need
to import the containing module. Afterwards, you can access the singleton via its
name. Reading its properties and writing to them inside JavaScript contexts is
done in the same way as with normal objects:

\code
import QtQuick
import MyModule

Item {
    x: MySingleton.posX
    Component.onCompleted: MySingleton.ready = true;
}
\endcode

Setting up bindings on a singletons properties is not possible; however, if it
is needed, a \l{Binding} element can be used to achieve the same result:
\code
import QtQuick
import MyModule

Item {
    id: root
    Binding {
        target: MySingleton
        property: "posX"
        value: root.x
    }
}
\endcode

\note Care must be taken when installing a binding on a singleton property: If
done by more than one file, the results are not defined.

\section1 Guidelines for (not) using singletons

Singletons allow you to expose data which needs to be accessed in multiple places
to the engine. That can be globally shared settings, like the spacing between
elements, or data models which need to be displayed in multiple places.
Compared to context properties which can solve a similar use case,
they have the benefit of being typed, being supported by tooling like the
\l{\QMLLS Reference}{\QMLLS}, and they are also generally faster at runtime.

It is recommended not to register too many singletons in a module: Singletons,
once created, stay alive until the engine itself gets destroyed
and come with the drawbacks of shared state as they are part of the global state.
Thus consider using the following techniques to reduce the amount of singletons
in your application:

\section2 Grouping together related data
Adding one singleton for each object which you want to expose adds quite some boiler plate.
Most of the time, it makes more sense to group data you want to expose together as properties
of a single singleton. Assume for instance that you want to create an ebook reader
where you need to expose three \l{QAbstractItemModel}{abstract item models}, one
for local books, and two for remote sources. Instead of repeating the process
for \l{Exposing an existing object as a singleton}{exposing existing objects}
three times, you can instead create one singleton and set it up before starting
the main application:
\code
class GlobalState : QObject
{
    Q_OBJECT
    QML_ELEMENT
    QML_SINGLETON
    Q_PROPERTY(QAbstractItemModel* localBooks MEMBER localBooks)
    Q_PROPERTY(QAbstractItemModel* digitalStoreFront MEMBER digitalStoreFront)
    Q_PROPERTY(QAbstractItemModel* publicLibrary MEMBER publicLibrary)
public:
    QAbstractItemModel* localBooks;
    QAbstractItemModel* digitalStoreFront;
    QAbstractItemModel* publicLibrary
};

int main() {
    QQmlApplicationEngine engine;
    auto globalState = engine.singletonInstance<GlobalState *>("MyModule", "GlobalState");
    globalState->localBooks = getLocalBooks();
    globalState->digitalStoreFront = setupLoalStoreFront();
    globalState->publicLibrary = accessPublicLibrary();
    engine.loadFromModule("MyModule", "Main");
}
\endcode

\section2 Use object instances
In the last section, we had the example of exposing three models as members of a
singleton. That can be useful when either the models need to be used in multiple
places, or when they are provided by some external API over which we have no
control. However, if we need the models only in a single place it might make
more sense have them as an instantiable type. Coming back to the previous example,
we can add an instantiable RemoteBookModel class, and then instantiate it inside
the book browser QML file:


\code
// remotebookmodel.h
class RemoteBookModel : public QAbstractItemModel
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
    // ...
};

// bookbrowser.qml
Row {
    ListView {
        model: RemoteBookModel { url: "www.public-lib.example"}
    }
    ListView {
        model: RemoteBookModel { url: "www.store-front.example"}
    }
}

\endcode

\section2 Passing initial state

While singletons can be used to pass state to QML, they are wasteful when the
state is only needed for the initial setup of the application. In that case, it
is often possible to use \l{QQmlApplicationEngine::setInitialProperties}.
You might for instance want to set \l{Window::visibility} to fullscreen if
a corresponding command line flag has been set:
\code
QQmlApplicationEngine engine;
if (parser.isSet(fullScreenOption)) {
    // assumes root item is ApplicationWindow
    engine.setInitialProperties(
        { "visibility", QVariant::fromValue(QWindow::FullScreen)}
    );
}
engine.loadFromModule("MyModule, "Main");
\endcode

*/