diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index 7934aea005..928b5b9752 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -19,8 +19,15 @@ FetchContent_Declare( FetchContent_MakeAvailable(Catch2 cpprealm) -# add_executable(examples testHelpers.hpp testHelpersTests.cpp authentication.cpp define-object-model.cpp examples.cpp flexible-sync.cpp supported-types.cpp) -add_executable(examples authentication.cpp define-object-model.cpp examples.cpp filter-data.cpp flexible-sync.cpp supported-types.cpp) +add_executable(examples + authentication.cpp + define-object-model.cpp + examples.cpp + filter-data.cpp + flexible-sync.cpp + quick-start.cpp + supported-types.cpp +) target_link_libraries(examples PRIVATE Catch2::Catch2WithMain) target_link_libraries(examples PRIVATE cpprealm) diff --git a/examples/cpp/quick-start.cpp b/examples/cpp/quick-start.cpp new file mode 100644 index 0000000000..03c1b23286 --- /dev/null +++ b/examples/cpp/quick-start.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +// :snippet-start: includes +#include +// :snippet-end: + +// :replace-start: { +// "terms": { +// "Local_": "", +// "Sync_": "", +// "userId.c_str()": "userId" +// } +// } + +static const std::string APP_ID = "cpp-tester-uliix"; + +struct Local_Todo : realm::object { + realm::persisted name; + realm::persisted status; + + static constexpr auto schema = realm::schema("Local_Todo", + realm::property<&Local_Todo::name>("name"), + realm::property<&Local_Todo::status>("status")); +}; + +// :snippet-start: model +struct Sync_Todo : realm::object { + realm::persisted _id{realm::object_id::generate()}; + realm::persisted name; + realm::persisted status; + // The ownerId property stores the user.identifier() of a + // logged-in user. Omit this property for the non-sync example. + realm::persisted ownerId; + + static constexpr auto schema = realm::schema("Sync_Todo", + realm::property<&Sync_Todo::_id, true>("_id"), + realm::property<&Sync_Todo::name>("name"), + realm::property<&Sync_Todo::status>("status"), + realm::property<&Sync_Todo::ownerId>("ownerId")); +}; +// :snippet-end: + +TEST_CASE("local quick start", "[realm][write]") { + // :snippet-start: realm-open + auto realm = realm::open(); + // :snippet-end: + + // :snippet-start: create-todo + auto todo = Local_Todo { + .name = "Create my first todo item", + .status = "In Progress" + }; + + realm.write([&realm, &todo] { + realm.add(todo); + }); + // :snippet-end: + + // :snippet-start: get-all-todos + auto todos = realm.objects(); + // :snippet-end: + CHECK(todos.size() == 1); + + // :snippet-start: filter + auto todosInProgress = todos.where([](auto const& todo) { + return todo.status == "In Progress"; + }); + // :snippet-end: + CHECK(todosInProgress.size() == 1); + + // :snippet-start: watch-for-changes + auto token = todo.observe([&](auto&& change) { + try { + if (change.error) { + rethrow_exception(change.error); + } + if (change.is_deleted) { + std::cout << "The object was deleted.\n"; + } else { + for (auto& propertyChange : change.property_changes) { + std::cout << "The object's " << propertyChange.name << " property has changed.\n"; + CHECK(propertyChange.name == "status"); // :remove: + } + } + } catch (std::exception const& e) { + std::cerr << "Error: " << e.what() << "\n"; + } + }); + // :snippet-end: + + // :snippet-start: modify-write-block + auto todoToUpdate = todosInProgress[0]; + realm.write([&realm, &todoToUpdate] { + todoToUpdate.status = "Complete"; + }); + // :snippet-end: + CHECK(*todoToUpdate.status == "Complete"); + + // :snippet-start: delete + realm.write([&realm, &todo] { + realm.remove(todo); + }); + // :snippet-end: +} + +TEST_CASE("sync quick start", "[realm][write][sync]") { + // :snippet-start: connect-to-backend + auto app = realm::App(APP_ID); + // :snippet-end: + + // :snippet-start: authenticate-user + auto user = app.login(realm::App::credentials::anonymous()).get_future().get(); + // :snippet-end: + + // :snippet-start: open-synced-realm + auto sync_config = user.flexible_sync_configuration(); + auto synced_realm_ref = realm::async_open(sync_config).get_future().get(); + auto realm = synced_realm_ref.resolve(); + // :remove-start: + // Remove any existing subscriptions before adding the one for this example + auto clearInitialSubscriptions = realm.subscriptions().update([](auto &subs) { + subs.clear(); + }).get_future().get(); + CHECK(clearInitialSubscriptions == true); + CHECK(realm.subscriptions().size() == 0); + // :remove-end: + // For this example, get the userId for the Flexible Sync query + auto userId = user.identifier(); + auto subscriptions = realm.subscriptions(); + auto updateSubscriptionSuccess = subscriptions.update([&](realm::mutable_sync_subscription_set &subs) { + subs.add("todos", [&userId](auto &obj) { + // For this example, get only Sync_Todo items where the ownerId + // property value is equal to the userId of the logged-in user. + return obj.ownerId == userId; + }); + }).get_future().get(); + // :snippet-end: + CHECK(updateSubscriptionSuccess == true); + + // The C++ SDK is currently missing a constructor to store a std::string + // So convert the userId std::string to a character array for persisting. + // TODO: Remove this and use the userId directly when the constructor is added. + // :snippet-start: write-to-synced-realm + auto todo = Sync_Todo { + .name = "Create a Sync todo item", + .status = "In Progress", + .ownerId = userId.c_str() + }; + + realm.write([&realm, &todo] { + realm.add(todo); + }); + + auto todos = realm.objects(); + // :snippet-end: + CHECK(todos.size() == 1); + + // The C++ SDK does not yet expose `waitForUpload` and `waitForDownload` + // so add a delay to prevent the connection from terminating while syncing + sleep(5); + realm.write([&realm, &todo] { + realm.remove(todo); + }); + sleep(5); +} + +// :replace-end: diff --git a/snooty.toml b/snooty.toml index ead41b7abf..caa914997a 100644 --- a/snooty.toml +++ b/snooty.toml @@ -14,6 +14,7 @@ toc_landing_pages = [ # SDKs "/sdk", "/sdk/cpp", + "/sdk/cpp/application-services", "/sdk/cpp/manage-users", "/sdk/java", "/sdk/java/api", diff --git a/source/examples/generated/cpp/quick-start.snippet.authenticate-user.cpp b/source/examples/generated/cpp/quick-start.snippet.authenticate-user.cpp new file mode 100644 index 0000000000..ef80320512 --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.authenticate-user.cpp @@ -0,0 +1 @@ +auto user = app.login(realm::App::credentials::anonymous()).get_future().get(); diff --git a/source/examples/generated/cpp/quick-start.snippet.connect-to-backend.cpp b/source/examples/generated/cpp/quick-start.snippet.connect-to-backend.cpp new file mode 100644 index 0000000000..fc41042859 --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.connect-to-backend.cpp @@ -0,0 +1 @@ +auto app = realm::App(APP_ID); diff --git a/source/examples/generated/cpp/quick-start.snippet.create-todo.cpp b/source/examples/generated/cpp/quick-start.snippet.create-todo.cpp new file mode 100644 index 0000000000..efc3b09f64 --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.create-todo.cpp @@ -0,0 +1,8 @@ +auto todo = Todo { + .name = "Create my first todo item", + .status = "In Progress" +}; + +realm.write([&realm, &todo] { + realm.add(todo); +}); diff --git a/source/examples/generated/cpp/quick-start.snippet.delete.cpp b/source/examples/generated/cpp/quick-start.snippet.delete.cpp new file mode 100644 index 0000000000..ab0583869c --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.delete.cpp @@ -0,0 +1,3 @@ +realm.write([&realm, &todo] { + realm.remove(todo); +}); diff --git a/source/examples/generated/cpp/quick-start.snippet.filter.cpp b/source/examples/generated/cpp/quick-start.snippet.filter.cpp new file mode 100644 index 0000000000..63e5d2dbbd --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.filter.cpp @@ -0,0 +1,3 @@ +auto todosInProgress = todos.where([](auto const& todo) { + return todo.status == "In Progress"; +}); diff --git a/source/examples/generated/cpp/quick-start.snippet.get-all-todos.cpp b/source/examples/generated/cpp/quick-start.snippet.get-all-todos.cpp new file mode 100644 index 0000000000..115138e0c7 --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.get-all-todos.cpp @@ -0,0 +1 @@ +auto todos = realm.objects(); diff --git a/source/examples/generated/cpp/quick-start.snippet.includes.cpp b/source/examples/generated/cpp/quick-start.snippet.includes.cpp new file mode 100644 index 0000000000..6b67f7eaca --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.includes.cpp @@ -0,0 +1 @@ +#include diff --git a/source/examples/generated/cpp/quick-start.snippet.model.cpp b/source/examples/generated/cpp/quick-start.snippet.model.cpp new file mode 100644 index 0000000000..d4dd7f4bfc --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.model.cpp @@ -0,0 +1,14 @@ +struct Todo : realm::object { + realm::persisted _id{realm::object_id::generate()}; + realm::persisted name; + realm::persisted status; + // The ownerId property stores the user.identifier() of a + // logged-in user. Omit this property for the non-sync example. + realm::persisted ownerId; + + static constexpr auto schema = realm::schema("Todo", + realm::property<&Todo::_id, true>("_id"), + realm::property<&Todo::name>("name"), + realm::property<&Todo::status>("status"), + realm::property<&Todo::ownerId>("ownerId")); +}; diff --git a/source/examples/generated/cpp/quick-start.snippet.modify-write-block.cpp b/source/examples/generated/cpp/quick-start.snippet.modify-write-block.cpp new file mode 100644 index 0000000000..bc7900e3c8 --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.modify-write-block.cpp @@ -0,0 +1,4 @@ +auto todoToUpdate = todosInProgress[0]; +realm.write([&realm, &todoToUpdate] { + todoToUpdate.status = "Complete"; +}); diff --git a/source/examples/generated/cpp/quick-start.snippet.open-synced-realm.cpp b/source/examples/generated/cpp/quick-start.snippet.open-synced-realm.cpp new file mode 100644 index 0000000000..681a385ad8 --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.open-synced-realm.cpp @@ -0,0 +1,13 @@ +auto sync_config = user.flexible_sync_configuration(); +auto synced_realm_ref = realm::async_open(sync_config).get_future().get(); +auto realm = synced_realm_ref.resolve(); +// For this example, get the userId for the Flexible Sync query +auto userId = user.identifier(); +auto subscriptions = realm.subscriptions(); +auto updateSubscriptionSuccess = subscriptions.update([&](realm::mutable_sync_subscription_set &subs) { + subs.add("todos", [&userId](auto &obj) { + // For this example, get only Todo items where the ownerId + // property value is equal to the userId of the logged-in user. + return obj.ownerId == userId; + }); +}).get_future().get(); diff --git a/source/examples/generated/cpp/quick-start.snippet.realm-open.cpp b/source/examples/generated/cpp/quick-start.snippet.realm-open.cpp new file mode 100644 index 0000000000..b41253df7e --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.realm-open.cpp @@ -0,0 +1 @@ +auto realm = realm::open(); diff --git a/source/examples/generated/cpp/quick-start.snippet.watch-for-changes.cpp b/source/examples/generated/cpp/quick-start.snippet.watch-for-changes.cpp new file mode 100644 index 0000000000..65ec8e1b6c --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.watch-for-changes.cpp @@ -0,0 +1,16 @@ +auto token = todo.observe([&](auto&& change) { + try { + if (change.error) { + rethrow_exception(change.error); + } + if (change.is_deleted) { + std::cout << "The object was deleted.\n"; + } else { + for (auto& propertyChange : change.property_changes) { + std::cout << "The object's " << propertyChange.name << " property has changed.\n"; + } + } + } catch (std::exception const& e) { + std::cerr << "Error: " << e.what() << "\n"; + } +}); diff --git a/source/examples/generated/cpp/quick-start.snippet.write-to-synced-realm.cpp b/source/examples/generated/cpp/quick-start.snippet.write-to-synced-realm.cpp new file mode 100644 index 0000000000..55c63be7cb --- /dev/null +++ b/source/examples/generated/cpp/quick-start.snippet.write-to-synced-realm.cpp @@ -0,0 +1,11 @@ +auto todo = Todo { + .name = "Create a Sync todo item", + .status = "In Progress", + .ownerId = userId +}; + +realm.write([&realm, &todo] { + realm.add(todo); +}); + +auto todos = realm.objects(); diff --git a/source/includes/note-unsupported-flex-sync-rql-operators.rst b/source/includes/note-unsupported-flex-sync-rql-operators.rst index 1d06f76ed2..cfb89ac81e 100644 --- a/source/includes/note-unsupported-flex-sync-rql-operators.rst +++ b/source/includes/note-unsupported-flex-sync-rql-operators.rst @@ -1,5 +1,5 @@ .. note:: Flexible Sync does not support all the query operators available in Realm - Query Language and LINQ. See :ref:`Flexible Sync RQL Limitations - ` for details. \ No newline at end of file + Query Language and the SDK's query engine. See :ref:`Flexible Sync RQL + Limitations ` for details. \ No newline at end of file diff --git a/source/sdk/cpp.txt b/source/sdk/cpp.txt index cfb56f16ef..51499c8b20 100644 --- a/source/sdk/cpp.txt +++ b/source/sdk/cpp.txt @@ -9,14 +9,14 @@ C++ SDK (Alpha) Why Realm Database? Install Realm + Quick Start Model Data Configure & Open a Realm CRUD React to Changes - Connect to App Services + Application Services Manage Users Manage Sync Subscriptions - Call an Atlas Function GitHub API Reference (Doxygen) diff --git a/source/sdk/cpp/application-services.txt b/source/sdk/cpp/application-services.txt new file mode 100644 index 0000000000..03ba88ae60 --- /dev/null +++ b/source/sdk/cpp/application-services.txt @@ -0,0 +1,131 @@ +.. _cpp-application-services: + +====================================== +Application Services - C++ SDK (Alpha) +====================================== + +.. toctree:: + :titlesonly: + + Connect to an App Services App + Call a Function + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +Realm SDKs let you connect your client apps to the Atlas App Services backend. +The SDKs provide the functionality +needed to authenticate users with any of the built-in :ref:`authentication +providers ` and call server-side :ref:`Functions +`. You can also use an App Services App to :ref:`sync data +between devices `. + +The App Client +-------------- +When using the SDK to access the App Services backend, you start with an +``App`` object. This object provides all other functionality related to +App Services. You initialize the ``App`` object with an App Services App ID, +which you can :ref:`find in the App Services UI `. + +.. tip:: + + To learn how to initialize the App client, see + :ref:`cpp-connect-to-backend`. + + +Authentication & User Management +-------------------------------- + +One of the most challenging aspects of client development is implementing a +robust and secure authentication system. With the Realm SDKs, however, you +can use any of the App Services authentication providers with very +minimal backend setup or client-side code required. With the authentication +APIs, you can implement the following functionality: + +- Creation of new user accounts +- User log-in and log-out +- Providing custom user data + +.. tip:: + + To learn how to set up authentication with different providers, refer to + :ref:`cpp-authenticate-users`. + + To learn how to provide custom user data, refer to :ref:`cpp-custom-user-data`. + +Calling Functions +----------------- + +:ref:`Atlas Functions ` enable you to define and execute server-side +logic for your application. You can call these Functions from your client applications +via the Realm SDKs. These server-side functions can run under the context +of the authenticated user, and thus honor the rules, roles, and permissions that +you have assigned to your collections. + +By using Functions, you provide a secure way for a variety of client +applications to share complex functionality without having to reproduce that +logic client-side. + +.. tip:: + + To learn how to call Functions, see :ref:`Call a Function `. + +.. _cpp-realm-sync: + +Sync Data +--------- + +Atlas Device Sync automatically synchronizes data between client applications and +an :ref:`App Services backend `. When a client +device is online, Sync asynchronously synchronizes data in a +background thread between the device and your backend App. + +When you use Sync in your client application, your implementation must match +the Sync Mode you select in your backend App configuration. The Realm C++ SDK +only supports Flexible Sync. + +.. seealso:: + + :ref:`enable-realm-sync` + +.. _cpp-flexible-sync-fundamentals: + +Flexible Sync +~~~~~~~~~~~~~ + +When you select :ref:`Flexible Sync ` for your backend App +configuration, your client implementation must include subscriptions to +queries on :ref:`queryable fields `. Flexible Sync works +by synchronizing data that matches query subscriptions you maintain in the +client application. + +A subscription set contains a set of queries. Flexible Sync returns +documents matching those queries, where the user has the appropriate +:ref:`permissions ` to read and/or +read and write the documents. If documents match the query, but the client +does not have the permission to read or write them, they do not sync to +the client application. + +You can form queries using :ref:`Realm Query Language `. + +.. include:: /includes/note-unsupported-flex-sync-rql-operators.rst + +Subscription sets are based on a specific type of :ref:`Realm object +`. You might have multiple subscriptions if you +have many types of Realm objects. + +To use Flexible Sync in your client application, open a synced realm +with a flexible sync configuration. Then, manage subscriptions +to determine which documents to sync. For more information, refer to +:ref:`cpp-manage-flexible-sync-subscriptions`. + +Group Updates for Improved Performance +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: /includes/sync-memory-performance.rst diff --git a/source/sdk/cpp/model-data/object-models.txt b/source/sdk/cpp/model-data/object-models.txt index c25308e0aa..362442afb5 100644 --- a/source/sdk/cpp/model-data/object-models.txt +++ b/source/sdk/cpp/model-data/object-models.txt @@ -10,6 +10,8 @@ Object Models - C++ SDK (Alpha) :depth: 3 :class: singlecol +.. _cpp-realm-objects: + Key Concept: Object Types & Schemas ----------------------------------- diff --git a/source/sdk/cpp/quick-start.txt b/source/sdk/cpp/quick-start.txt new file mode 100644 index 0000000000..ccd0a6beb9 --- /dev/null +++ b/source/sdk/cpp/quick-start.txt @@ -0,0 +1,170 @@ +.. _cpp-client-quick-start: +.. _cpp-client-quick-start-with-sync: + +============================= +Quick Start - C++ SDK (Alpha) +============================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +This Quick Start demonstrates how to use Realm Database with the Realm C++ SDK. +Before you begin, ensure you have :ref:`Installed the C++ SDK `. + +Import Realm +------------ + +Make the Realm C++ SDK available in your code by including the +``cpprealm/sdk.hpp`` header in the translation unit where you want to use it: + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.includes.cpp + :language: cpp + +Define Your Object Model +------------------------ + +For a local-only Realm Database you can define your :ref:`object model +` directly in code. In this quick start, you can remove +``ownerId`` unless you want to add the optional Device Sync. + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.model.cpp + :language: cpp + +Open a Realm +------------ + +In a local-only Realm Database, the simplest option to open a realm +is to use the default realm with no configuration parameter: + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.realm-open.cpp + :language: cpp + +You can also specify a :cpp-sdk:`db_config +` to open a realm +at a specific path, or to provide a ``sync_config`` to open a synced realm. + +For more information, see: :ref:`Configure and Open a Realm +`. + +Create, Read, Update, and Delete Objects +---------------------------------------- + +Once you have opened a realm, you can modify it and its :ref:`objects +` in a :ref:`write transaction ` +block. + +To instantiate a new Todo object and add it to the realm in a write block: + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.create-todo.cpp + :language: cpp + +You can retrieve a live :ref:`results collection ` of +all todos in the realm: + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.get-all-todos.cpp + :language: cpp + +You can also filter that collection using :ref:`where `: + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.filter.cpp + :language: cpp + +To modify a todo, update its properties in a write transaction block: + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.modify-write-block.cpp + :language: cpp + +Finally, you can delete a todo: + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.delete.cpp + :language: cpp + +Watch for Changes +----------------- + +You can watch an :ref:`object for changes +` with the ``observe`` method. + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.watch-for-changes.cpp + :language: cpp + +Add Device Sync (Optional) +-------------------------- + +If you want to sync Realm data across devices, you can set up an Atlas +App Services App and enable Device Sync. For more information on what +you can do with App Services, see: :ref:`cpp-application-services`. + +Prerequisites +~~~~~~~~~~~~~ + +Before you can sync Realm data, you must: + +- :ref:`Create an App Services App ` +- :ref:`Enable anonymous authentication ` +- :ref:`Enable Flexible Sync ` with :ref:`Development Mode + ` toggled to ``On``. This example requires an ``ownerId`` + field in your Device Sync :guilabel:`Queryable Fields` section. + +.. _cpp-quick-start-init-app: + +Initialize the App +~~~~~~~~~~~~~~~~~~ + +.. include:: /includes/access-app-id.rst + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.connect-to-backend.cpp + :language: cpp + +.. _cpp-quick-start-authenticate: + +Authenticate a User +~~~~~~~~~~~~~~~~~~~ + +In this quick start, you use :ref:`anonymous authentication ` +to log in users without requiring them to provide any identifying information. +After authenticating the user, you can open a realm for that user. + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.authenticate-user.cpp + :language: cpp + +The Realm C++ SDK provides many additional ways to authenticate, register, and link users. +For other authentication providers, see: :ref:`cpp-authenticate-users` + +Open a Realm +~~~~~~~~~~~~ + +Once you have enabled Device Sync and authenticated a user, you can create +a :cpp-sdk:`sync_configuration +` object and +open the realm. You can then add a :ref:`Flexible Sync +subscription ` that determines +what data the realm can read and write. + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.open-synced-realm.cpp + :language: cpp + +Read, Write, and React to Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The syntax to :ref:`read `, :ref:`write +`, and :ref:`watch for changes ` on a +synced realm is identical to the syntax for non-synced realms above. + +The only difference here is that this example stores the ``user.identifier()`` +of the logged-in user in the ``ownerId`` property of the ``Todo`` item. +This lets us query for only the user's todos in the subscription, and set +Sync permissions to :guilabel:`Users can only read and write their own data`. + +For more information about Sync permissions, refer to :ref:`sync-permissions`. + +.. literalinclude:: /examples/generated/cpp/quick-start.snippet.write-to-synced-realm.cpp + :language: cpp + +While you work with local data, a background thread efficiently integrates, +uploads, and downloads changesets. + +.. include:: /includes/sync-memory-performance.rst