summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorKonrad Kujawa <konrad.kujawa@qt.io>2022-08-24 16:00:11 +0200
committerKonrad Kujawa <konrad.kujawa@qt.io>2022-09-08 09:31:10 +0200
commitce3ba5df2a011033f366fcc7279198f732ae311a (patch)
tree0393116e8b0f8a89a1bd2b8fb2878c9aa51da27a /examples
parentd87e31ab366a8d99ca2af3dcdd85eb87d2a20ccb (diff)
Qt Quick UI for the RESTful Address Book
Add UI and client side extension for the RESTful Address Book example Fixes: QTBUG-105199 Change-Id: I01c5e2ac44838f01ccbb73d5b6e1d1e578c67618 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Alex Blasche <alexander.blasche@qt.io> (cherry picked from commit 91e567ba53bf8c7be9aff8feb00e46f978937290) Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'examples')
-rw-r--r--examples/demos/addressbook/CMakeLists.txt58
-rw-r--r--examples/demos/addressbook/addressbookmodel.cpp127
-rw-r--r--examples/demos/addressbook/addressbookmodel.h50
-rw-r--r--examples/demos/addressbook/contactentry.h29
-rw-r--r--examples/demos/addressbook/doc/images/addressbookclient.pngbin0 -> 39190 bytes
-rw-r--r--examples/demos/addressbook/doc/images/authorize.pngbin0 -> 33509 bytes
-rw-r--r--examples/demos/addressbook/doc/images/newcontact.pngbin0 -> 46340 bytes
-rw-r--r--examples/demos/addressbook/doc/src/addressbook-client-example.qdoc70
-rw-r--r--examples/demos/addressbook/main.cpp38
-rw-r--r--examples/demos/addressbook/qml/main.qml173
-rw-r--r--examples/demos/addressbook/restaccessmanager.cpp114
-rw-r--r--examples/demos/addressbook/restaccessmanager.h56
12 files changed, 715 insertions, 0 deletions
diff --git a/examples/demos/addressbook/CMakeLists.txt b/examples/demos/addressbook/CMakeLists.txt
new file mode 100644
index 000000000..7572f4dc8
--- /dev/null
+++ b/examples/demos/addressbook/CMakeLists.txt
@@ -0,0 +1,58 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(addressbookclient LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/demos/${PROJECT_NAME}")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)
+
+qt_add_executable(addressbookclient
+ addressbookmodel.h addressbookmodel.cpp
+ restaccessmanager.h restaccessmanager.cpp
+ main.cpp
+)
+
+set(qml_files
+ "qml/main.qml"
+)
+
+qt_add_resources(addressbookclient "qml"
+ PREFIX
+ "/"
+ FILES
+ ${qml_files}
+)
+
+set_target_properties(addressbookclient PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+qt_add_qml_module(addressbookclient
+ URI AddressBookModel
+ VERSION 1.0
+ QML_FILES
+ "qml/main.qml"
+ NO_RESOURCE_TARGET_PATH
+)
+
+target_link_libraries(addressbookclient PRIVATE
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+)
+
+install(TARGETS addressbookclient
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/demos/addressbook/addressbookmodel.cpp b/examples/demos/addressbook/addressbookmodel.cpp
new file mode 100644
index 000000000..83422964f
--- /dev/null
+++ b/examples/demos/addressbook/addressbookmodel.cpp
@@ -0,0 +1,127 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "addressbookmodel.h"
+#include "contactentry.h"
+
+#include <QMutexLocker>
+
+AddressBookModel::AddressBookModel(QSharedPointer<RestAccessManager> manager, QObject *parent)
+ : QAbstractTableModel(parent), accessManager(std::move(manager))
+{
+ QObject::connect(accessManager.get(), &RestAccessManager::contactsChanged, this,
+ &AddressBookModel::updateContacts);
+}
+
+AddressBookModel::~AddressBookModel() = default;
+
+int AddressBookModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ QMutexLocker lock(&contactsMtx);
+ return contacts.size();
+}
+
+int AddressBookModel::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return ContactEntry::size();
+}
+
+QVariant AddressBookModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Orientation::Horizontal) {
+ switch (section) {
+ case 0:
+ return ""; // ID is not shown
+ case 1:
+ return "Name";
+ case 2:
+ return "Address";
+ }
+ }
+ return QVariant();
+}
+
+QVariant AddressBookModel::data(const QModelIndex &index, int role) const
+{
+ QMutexLocker lock(&contactsMtx);
+
+ if (index.row() < 0 || index.row() >= contacts.count())
+ return QVariant();
+
+ const ContactEntry &contact = contacts.at(index.row());
+ QVariant ret;
+
+ switch (role) {
+ case AddressBookRoles::IdRole:
+ ret = contact.id;
+ break;
+
+ case AddressBookRoles::NameRole:
+ ret = contact.name;
+ break;
+
+ case AddressBookRoles::AddressRole:
+ ret = contact.address;
+ break;
+ }
+ return ret;
+}
+
+QHash<int, QByteArray> AddressBookModel::roleNames() const
+{
+ QHash<int, QByteArray> roles;
+ roles[AddressBookRoles::IdRole] = "id";
+ roles[AddressBookRoles::NameRole] = "name";
+ roles[AddressBookRoles::AddressRole] = "address";
+ return roles;
+}
+
+Q_INVOKABLE void AddressBookModel::setAuthorizationHeader(const QString &key, const QString &value)
+{
+ accessManager->setAuthorizationHeader(key, value);
+}
+
+Q_INVOKABLE void AddressBookModel::addContact(const QString &name, const QString &contact)
+{
+ accessManager->addContact(ContactEntry{ 0, name, contact });
+}
+
+Q_INVOKABLE void AddressBookModel::updateContact(qint64 id, const QString &name,
+ const QString &contact)
+{
+ accessManager->updateContact(ContactEntry{ id, name, contact });
+}
+
+Q_INVOKABLE void AddressBookModel::removeContact(const qint64 id)
+{
+ accessManager->deleteContact(id);
+}
+
+Q_INVOKABLE void AddressBookModel::refresh()
+{
+ this->updateContacts();
+}
+
+void AddressBookModel::updateContacts()
+{
+ auto toContactEntry = [](const auto &it) {
+ return ContactEntry{ it.first, it.second.name, it.second.address };
+ };
+ QList<ContactEntry> tmpContacts;
+ const RestAccessManager::ContactsMap contactsMap = accessManager->getContacts();
+ std::transform(contactsMap.constKeyValueBegin(), contactsMap.constKeyValueEnd(),
+ std::back_inserter(contacts), toContactEntry);
+
+ this->beginInsertRows(this->index(-1, -1), 0, this->rowCount());
+ {
+ QMutexLocker lock(&contactsMtx);
+ contacts.swap(tmpContacts);
+ }
+ this->endInsertRows();
+}
diff --git a/examples/demos/addressbook/addressbookmodel.h b/examples/demos/addressbook/addressbookmodel.h
new file mode 100644
index 000000000..e7b8d6027
--- /dev/null
+++ b/examples/demos/addressbook/addressbookmodel.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef ADDRESSBOOKMODEL_H
+#define ADDRESSBOOKMODEL_H
+
+#include "contactentry.h"
+#include "restaccessmanager.h"
+
+#include <QAbstractItemModel>
+#include <QMutex>
+#include <QSharedPointer>
+#include <QtQml/qqml.h>
+
+class AddressBookModel : public QAbstractTableModel
+{
+ Q_OBJECT
+ QML_ELEMENT
+public:
+ enum AddressBookRoles { IdRole = Qt::UserRole + 1, NameRole, AddressRole };
+ Q_ENUM(AddressBookRoles)
+
+ explicit AddressBookModel(QSharedPointer<RestAccessManager> manager, QObject *parent = nullptr);
+ ~AddressBookModel();
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ Q_INVOKABLE void setAuthorizationHeader(const QString &key, const QString &value);
+ Q_INVOKABLE void addContact(const QString &name, const QString &contact);
+ Q_INVOKABLE void updateContact(qint64 id, const QString &name, const QString &contact);
+ Q_INVOKABLE void removeContact(qint64 id);
+ Q_INVOKABLE void refresh();
+
+signals:
+ void contactsChanged();
+
+private slots:
+ void updateContacts();
+
+private:
+ QSharedPointer<RestAccessManager> accessManager;
+ mutable QMutex contactsMtx;
+ QList<ContactEntry> contacts;
+};
+
+#endif // ADDRESSBOOKMODEL_H
diff --git a/examples/demos/addressbook/contactentry.h b/examples/demos/addressbook/contactentry.h
new file mode 100644
index 000000000..e70e629fa
--- /dev/null
+++ b/examples/demos/addressbook/contactentry.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef TYPES_H
+#define TYPES_H
+
+#include <QJsonObject>
+#include <QString>
+
+struct ContactEntry
+{
+ qint64 id;
+ QString name;
+ QString address;
+
+ QJsonObject toJson() const
+ {
+ return QJsonObject{ { "id", id }, { "name", name }, { "address", address } };
+ }
+
+ static constexpr qsizetype size() { return 3; }
+
+ bool operator==(const ContactEntry &other) const
+ {
+ return name == other.name && address == other.address;
+ }
+};
+
+#endif // TYPES_H
diff --git a/examples/demos/addressbook/doc/images/addressbookclient.png b/examples/demos/addressbook/doc/images/addressbookclient.png
new file mode 100644
index 000000000..9ed284464
--- /dev/null
+++ b/examples/demos/addressbook/doc/images/addressbookclient.png
Binary files differ
diff --git a/examples/demos/addressbook/doc/images/authorize.png b/examples/demos/addressbook/doc/images/authorize.png
new file mode 100644
index 000000000..16ad5e709
--- /dev/null
+++ b/examples/demos/addressbook/doc/images/authorize.png
Binary files differ
diff --git a/examples/demos/addressbook/doc/images/newcontact.png b/examples/demos/addressbook/doc/images/newcontact.png
new file mode 100644
index 000000000..df351c371
--- /dev/null
+++ b/examples/demos/addressbook/doc/images/newcontact.png
Binary files differ
diff --git a/examples/demos/addressbook/doc/src/addressbook-client-example.qdoc b/examples/demos/addressbook/doc/src/addressbook-client-example.qdoc
new file mode 100644
index 000000000..23bcfe37f
--- /dev/null
+++ b/examples/demos/addressbook/doc/src/addressbook-client-example.qdoc
@@ -0,0 +1,70 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\ingroup qtquickdemos
+\example demos/addressbook
+\title Qt Quick Demo - RESTful API client Address Book
+\brief Example of how to create a RESTful API client.
+\image addressbookclient.png
+
+This example shows how to create a basic QML application with address book
+functionality.
+The application uses RESTful communication with a given server to send
+requests and retrieve data.
+
+The application allows users to add new contacts by clicking the
+'Add Contact' button and then entering the data for the record and
+clicking the 'Add' button (see image below).
+\image newcontact.png
+
+The Address Book application gives you the ability to delete an entry,
+by clicking the 'Delete' button next to the entry, and update by updating
+the data in the table.
+
+In order to use the modification features, users must authorize themselves
+by providing a key and value, which will be used in communication
+with the RESTful API.
+\image authorize.png
+
+To run the client application, first run the
+\l {RESTful server Address Book Example} {Address Book server example}
+in the background or use an already running server that provides used API.
+Then run the client application, to run it host and port arguments must be provided,
+for example:
+\code
+./addressbookclient --host http://127.0.0.1 --port 62122
+\endcode
+
+This example application uses QNetworkAccessManager
+which is wrapped in the \c RestAccessManager class.
+
+\snippet demos/addressbook/restaccessmanager.cpp Connect QNetworkAccessManager example
+The code snippet above shows how to connect QNetworkAccessManager to this wrapper.
+First, a connection to the server is established
+and the QNetworkAccessManager::setAutoDeleteReplies method is called to simplify
+the QNetworkReply deletion.
+Then QObject::connect is used to call the internal \c RestAccessManager::readContacts
+after the QNetworkReply is ready to be processed.
+
+\snippet demos/addressbook/restaccessmanager.cpp Update contacts signal example
+This method asynchronously processes each QNetworkReply
+for each request sent via QNetworkAccessManager.
+When the response is an array, that practically means that RestAccessManage
+got a new list of the contacts, so it has to update it.
+When the response is different, it means that the corresponding request
+has changed the list of contacts and it needs to be retrieved from the server.
+
+\snippet demos/addressbook/restaccessmanager.cpp GET contacts example
+To send a \c GET request, the QNetworkAccessManager::get method is used
+with the prepared QNetworkRequest.
+QNetworkRequest::setHeader is used to ensure correct encoding of the content.
+
+\snippet demos/addressbook/restaccessmanager.cpp POST contacts example
+To send the \c POST request, a similar approach can be used.
+In addition, to set the authorization header
+QNetworkRequest::setRawHeader was used.
+
+
+\sa {RESTful server Address Book Example}
+*/
diff --git a/examples/demos/addressbook/main.cpp b/examples/demos/addressbook/main.cpp
new file mode 100644
index 000000000..f419e5b36
--- /dev/null
+++ b/examples/demos/addressbook/main.cpp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "addressbookmodel.h"
+#include "restaccessmanager.h"
+
+#include <QCommandLineParser>
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QCommandLineParser parser;
+ parser.addOptions({
+ { "host", QCoreApplication::translate("main", "Hostname of the server."), "host" },
+ { "port", QCoreApplication::translate("main", "Port of the server."), "port" },
+ });
+ parser.addHelpOption();
+ parser.process(app);
+
+ if (parser.value("host").isEmpty() || parser.value("port").isEmpty())
+ parser.showHelp(-1);
+
+ QQmlApplicationEngine engine;
+ auto manager = QSharedPointer<RestAccessManager>::create(parser.value("host"),
+ parser.value("port").toInt());
+ AddressBookModel model(manager);
+ engine.setInitialProperties({ { "addressBookModel", QVariant::fromValue(&model) } });
+
+ engine.load(QUrl("qrc:/qml/main.qml"));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ return QGuiApplication::exec();
+}
diff --git a/examples/demos/addressbook/qml/main.qml b/examples/demos/addressbook/qml/main.qml
new file mode 100644
index 000000000..2505cd40b
--- /dev/null
+++ b/examples/demos/addressbook/qml/main.qml
@@ -0,0 +1,173 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Window
+import Qt.labs.qmlmodels
+
+import AddressBookModel
+
+ApplicationWindow {
+ id: root
+ width: 850
+ height: 350
+ visible: true
+ title: qsTr("Address Book")
+ required property AddressBookModel addressBookModel
+
+ Button {
+ id: newContactButton
+ text: qsTr("Add new contact")
+ onClicked: newContactPopup.open()
+ }
+ Popup {
+ id: newContactPopup
+ padding: 10
+ anchors.centerIn: parent
+ modal: true
+ focus: true
+
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+
+ GridLayout {
+ columns: 2
+ TextField {
+ id: newNameField
+ placeholderText: qsTr("Enter name")
+ padding: 10
+ }
+ TextField {
+ id: newAddressField
+ placeholderText: qsTr("Enter address")
+ padding: 10
+ }
+ Button {
+ text: qsTr("Cancel")
+ onClicked: {
+ newContactPopup.close();
+ }
+ }
+ Button {
+ text: qsTr("Add")
+ onClicked: {
+ addressBookModel.addContact(newNameField.displayText,
+ newAddressField.displayText);
+ newContactPopup.close();
+ }
+ }
+ }
+ }
+
+ Button {
+ id: authorizeButton
+ text: qsTr("Authorize")
+ onClicked: authorizePopup.open()
+ anchors {
+ top: newContactButton.bottom
+ left: newContactButton.left
+ }
+ }
+ Popup {
+ id: authorizePopup
+ padding: 10
+ anchors.centerIn: parent
+ modal: true
+ focus: true
+
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+
+ GridLayout {
+ columns: 2
+ TextField {
+ id: apiKeyField
+ text: "api_key"
+ padding: 10
+ readOnly: true
+ }
+ TextField {
+ id: apiKeyValueField
+ placeholderText: qsTr("Enter API key value")
+ padding: 10
+ }
+ Button {
+ text: qsTr("Cancel")
+ onClicked: {
+ authorizePopup.close();
+ }
+ }
+ Button {
+ text: qsTr("Set")
+ onClicked: {
+ addressBookModel.setAuthorizationHeader(apiKeyField.displayText,
+ apiKeyValueField.displayText);
+ authorizePopup.close();
+ }
+ }
+ }
+ }
+
+ HorizontalHeaderView {
+ id: horizontalHeader
+ model: addressBookModel
+ syncView: tableView
+ anchors {
+ top: newContactButton.top
+ left: newContactButton.right
+ }
+ }
+
+ TableView {
+ id: tableView
+ model: addressBookModel
+ anchors {
+ top: horizontalHeader.bottom
+ left: horizontalHeader.left
+ }
+ width: 700
+ height: 350
+ columnSpacing: 1
+ rowSpacing: 1
+ clip: true
+ property var columnWidthsFactor: [0.15, 0.35, 0.5]
+ columnWidthProvider: function (column) {
+ return tableView.model ? tableView.width * columnWidthsFactor[column] : 0;
+ }
+ rowHeightProvider: function (row) {
+ return 50;
+ }
+
+ delegate: DelegateChooser {
+ DelegateChoice {
+ column: 0
+ delegate: Rectangle {
+ Button {
+ id: deleteButton
+ text: qsTr("Delete")
+ onClicked: addressBookModel.removeContact(id)
+ }
+ }
+ }
+ DelegateChoice {
+ column: 1
+ delegate: Rectangle {
+ TextInput {
+ text: name
+ padding: 10
+ onEditingFinished: addressBookModel.updateContact(id, displayText, address)
+ }
+ }
+ }
+ DelegateChoice {
+ column: 2
+ delegate: Rectangle {
+ TextInput {
+ text: address
+ padding: 10
+ onEditingFinished: addressBookModel.updateContact(id, name, displayText)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/addressbook/restaccessmanager.cpp b/examples/demos/addressbook/restaccessmanager.cpp
new file mode 100644
index 000000000..7b746a085
--- /dev/null
+++ b/examples/demos/addressbook/restaccessmanager.cpp
@@ -0,0 +1,114 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "restaccessmanager.h"
+
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QNetworkReply>
+
+static std::optional<QJsonArray> byteArrayToJsonArray(const QByteArray &arr)
+{
+ QJsonParseError err;
+ const auto json = QJsonDocument::fromJson(arr, &err);
+ if (err.error || !json.isArray())
+ return std::nullopt;
+ return json.array();
+}
+
+RestAccessManager::RestAccessManager(const QString &host, quint16 port) : host(host), port(port)
+{
+ //! [Connect QNetworkAccessManager example]
+ manager.connectToHost(host, port);
+ manager.setAutoDeleteReplies(true);
+ QObject::connect(&manager, &QNetworkAccessManager::finished, this,
+ &RestAccessManager::readContacts);
+ //! [Connect QNetworkAccessManager example]
+ this->updateContacts();
+}
+
+RestAccessManager::~RestAccessManager() = default;
+
+void RestAccessManager::setAuthorizationHeader(const QString &key, const QString &value)
+{
+ authHeader = AuthHeader{ key, value };
+}
+
+RestAccessManager::ContactsMap RestAccessManager::getContacts() const
+{
+ QMutexLocker lock(&contactsMtx);
+ return contacts;
+}
+
+//! [POST contacts example]
+void RestAccessManager::addContact(const ContactEntry &entry)
+{
+ auto request = QNetworkRequest(QUrl(QString("%1:%2/v2/contact").arg(host).arg(port)));
+ request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
+ if (authHeader) {
+ request.setRawHeader(authHeader->key.toLatin1(), authHeader->value.toLatin1());
+ }
+ manager.post(request, QJsonDocument(entry.toJson()).toJson(QJsonDocument::Compact));
+}
+//! [POST contacts example]
+
+void RestAccessManager::updateContact(const ContactEntry &entry)
+{
+ auto request =
+ QNetworkRequest(QUrl(QString("%1:%2/v2/contact/%3").arg(host).arg(port).arg(entry.id)));
+ request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
+ if (authHeader) {
+ request.setRawHeader(authHeader->key.toLatin1(), authHeader->value.toLatin1());
+ }
+ manager.put(request, QJsonDocument(entry.toJson()).toJson(QJsonDocument::Compact));
+}
+
+void RestAccessManager::deleteContact(qint64 id)
+{
+ auto request =
+ QNetworkRequest(QUrl(QString("%1:%2/v2/contact/%3").arg(host).arg(port).arg(id)));
+ if (authHeader) {
+ request.setRawHeader(authHeader->key.toLatin1(), authHeader->value.toLatin1());
+ }
+ manager.deleteResource(request);
+}
+
+//! [Update contacts signal example]
+void RestAccessManager::readContacts(QNetworkReply *reply)
+{
+ if (reply->error()) {
+ return;
+ }
+ const std::optional<QJsonArray> array = byteArrayToJsonArray(reply->readAll());
+ if (array) {
+ ContactsMap tmpContacts;
+ for (const auto &jsonValue : *array) {
+ if (jsonValue.isObject()) {
+ const QJsonObject obj = jsonValue.toObject();
+ if (obj.contains("id") && obj.contains("name") && obj.contains("address")) {
+ tmpContacts.insert(obj.value("id").toInt(),
+ ContactEntry{ obj.value("id").toInt(),
+ obj.value("name").toString(),
+ obj.value("address").toString() });
+ }
+ }
+ }
+ {
+ QMutexLocker lock(&contactsMtx);
+ contacts.swap(tmpContacts);
+ }
+ emit contactsChanged();
+ } else {
+ this->updateContacts();
+ }
+}
+//! [Update contacts signal example]
+
+//! [GET contacts example]
+void RestAccessManager::updateContacts()
+{
+ auto request = QNetworkRequest(QUrl(QString("%1:%2/v2/contact").arg(host).arg(port)));
+ request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
+ manager.get(request);
+}
+//! [GET contacts example]
diff --git a/examples/demos/addressbook/restaccessmanager.h b/examples/demos/addressbook/restaccessmanager.h
new file mode 100644
index 000000000..19d417ede
--- /dev/null
+++ b/examples/demos/addressbook/restaccessmanager.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef RESTACCESSMANAGER_H
+#define RESTACCESSMANAGER_H
+
+#include "contactentry.h"
+
+#include <QMap>
+#include <QMutex>
+#include <QNetworkAccessManager>
+#include <QObject>
+#include <QString>
+
+#include <optional>
+
+class RestAccessManager : public QObject
+{
+ Q_OBJECT
+ struct AuthHeader
+ {
+ QString key;
+ QString value;
+ };
+
+public:
+ using ContactsMap = QMap<qint64, ContactEntry>;
+
+ explicit RestAccessManager(const QString &host, quint16 port);
+ ~RestAccessManager();
+
+ void setAuthorizationHeader(const QString &key, const QString &value);
+ ContactsMap getContacts() const;
+
+ void addContact(const ContactEntry &entry);
+ void updateContact(const ContactEntry &entry);
+ void deleteContact(qint64 id);
+
+signals:
+ void contactsChanged();
+
+private slots:
+ void readContacts(QNetworkReply *reply);
+
+private:
+ void updateContacts();
+
+ const QString host;
+ const quint16 port;
+ std::optional<AuthHeader> authHeader;
+ QNetworkAccessManager manager;
+ mutable QMutex contactsMtx;
+ ContactsMap contacts;
+};
+
+#endif // RESTACCESSMANAGER_H