Docs 菜单
Docs 主页
/ /
Atlas App Services

教程:使用 FTXUI 的 Atlas Device Sync for C++

在此页面上

  • 学习目标
  • 先决条件
  • 从模板开始
  • 探索模板应用
  • 打开应用
  • 构建应用程序
  • 探索应用结构
  • 运行应用
  • 检查后端
  • 修改应用程序
  • 添加新属性
  • 向模型添加属性
  • 在用户界面中添加元素以设置优先级
  • 将新属性保存到数据库
  • 运行和测试
  • 更改订阅
  • 更新订阅
  • 运行和测试
  • 结论
  • 接下来的步骤

预计完成时间:30 分钟,具体取决于您使用 C++ 的经验

适用于C++的Atlas Device SDK使您能够跨手机、平板电脑、可穿戴设备或物联网(IoT)设备存储和同步数据。 本教程基于名为 的C++模板应用程序,该应用程序说明了如何创建使用cpp.todo.flex FTXUI 构建的待办事项列表终端 GUI应用程序 。该应用程序使用户能够:

  • 将他们的电子邮件注册为新用户帐户。

  • 使用电子邮件和密码登录他们的帐户(稍后退出)。

  • 查看、创建、修改和删除自己的任务。

  • 查看所有任务,即使用户不是所有者。

模板应用还提供了一个切换开关,用于模拟处于“离线模式”的设备。此切换开关可以让您在模拟器上模拟没有互联网连接的用户,以快速测试 Device Sync 功能。但是,您可能会在生产应用程序中删除此切换开关。

本教程基于模板应用构建。您可向现有 Item 模型添加一个新的 priority 字段,并更新灵活同步订阅以仅显示优先级范围内的项目。

本教程说明如何根据自己的需要调整模板应用。考虑到模板应用的当前结构,您不一定会进行该更改。

在本教程中,您将学习如何:

  • 使用非破坏性变更更新 C++ 对象模型。

  • 更新 Device Sync 订阅。

  • 将可查询字段添加到服务器上的 Device Sync 配置中,以更改要同步的数据。

提示

如果更想从自己的应用程序开始,而不是跟随引导式教程,则请查看 C++ 快速入门。其中包括可复制的代码示例,以及设置 Atlas App Services 后端所需的基本信息。

  • 确保您已安装必要的软件。C++ 模板应用假定您拥有:

    • CMake 版本 3.25 或更新版本。

    • C++ 17 或更高版本。

  • 本教程从“模板应用程序”开始。创建“模板应用程序”需要 Atlas 帐号、API 密钥和 App Services CLI。

    • 如需了解有关创建 Atlas 帐户的更多信息,请参阅 Atlas 入门文档。在本教程中,您需要一个带有免费层级集群的 Atlas 帐户。

    • 您还需要一个用于登录的 MongoDB Cloud 账户的 Atlas API 密钥。您必须是项目所有者才能使用 App Services CLI 创建模板应用。

    • 要了解有关安装 App Services CLI 的更多信息,请参阅安装 App Services CLI 。安装后,使用 Atlas 项目的 API 密钥运行登录命令。

本教程基于名为 cpp.todo.flex 的 C++ Sync 模板应用。我们先使用默认应用,然后在其上构建新功能。

要了解有关模板应用的更多信息,请参阅模板应用

如果您还没有 Atlas 帐号,请注册以部署“模板应用程序”。

按照“创建App Services App”指南中描述的步骤操作,然后选择 Create App from Template 。 选择Real-time Sync模板。 这将创建一个预先配置为与Device Sync模板应用客户端之一一起使用的App Services App 。

创建模板应用后,用户界面会显示一个标有 Get the Front-end Code for your Template 的模态窗口。此模态窗口提供有关将模板应用客户端代码下载为 .zip 文件或使用 App Services CLI 获取客户端的说明。

尚无法在App Services用户界面中下载C++模板应用。 使用CLI或从Github克隆存储库以获取客户端代码。

appservices apps create命令设置后端并创建C++模板应用以用作本教程的基础。

在终端窗口中运行以下命令,创建一个名为“MyTutorialApp”的应用,将该应用部署在 US-VA 地区,并将其环境设置为“开发”(而不是生产或 QA)。

appservices app create \
--name MyTutorialApp \
--template cpp.todo.flex \
--deployment-model global \
--environment development

该命令在当前路径中创建一个新目录,其名称与 --name 标志的值相同。

Github您可以创建分支并克隆包含Device Sync 客户端代码的 存储库。C++客户端代码位于 https://github.com/mongodb/template-app-cpp-todo。

如果您使用此过程来获取客户端代码,则必须创建一个模板应用以配合客户端使用。按照创建模板应用中的说明操作,使用 Atlas App Services 用户界面、App Services CLI 或 Admin API 创建 Device Sync 模板应用。

1

在您的首选 IDE 中打开前端客户端代码。

如果从Github存储库库克隆客户端,则必须手动将App Services App ID插入客户端中的相应位置。 按照客户端README.md中的Configuration说明来学习;了解在何处插入 App ID。

2
  1. 创建一个用于构建应用的目录。为方便起见,与模板应用一起打包的 .gitignore 会忽略客户端目录中的 build 目录。导航到构建目录。

    mkdir build && cd build
  2. 使用 CMake 创建 Makefile。假设您是从客户端目录中的 build 目录构建的:

    cmake ../
  3. 使用 CMake 构建应用可执行文件。这需要一些时间,因为它会安装依赖项并编译可执行文件。

    cmake --build .
3

花几分钟时间了解在 CMake 构建可执行文件时项目的组织方式。

在本教程中您不会直接使用这些文件,但它们包含演示如何使用 C++ SDK 的代码:

file
用途
controllers/app_controller.cpp

使用 nlohmann::json 库读取 atlasConfig.json 中的值。然后,使用这些值初始化 realm::App,并将其保存在应用程序的状态中。App 是您的应用程序与 App Services 后端通信的方式。这提供了对身份验证以及已登录用户的 realm::user 对象的访问权限。

要进一步了解如何自定义应用配置,请参阅:连接到 Atlas App Services 后端

此代码还设置 AuthManagerErrorManager。它包含导航流,根据用户是否已登录,将用户导向 LoginControllerHomeController

managers/auth_manager.cpp
注册使用电子邮件/密码的用户、登录或退出以及在发生身份验证错误时显示错误消息的逻辑。

在本教程中,你将使用以下文件:

file
用途
state/item.hpp
定义我们在数据库中存储的 Item 对象。
state/home_controller_state.hpp
管理主页视图的应用状态。
controllers/home_controller.hpp
包含主页视图控制器的重要定义。
controllers/home_controller.cpp
实现主页视图。这是登录用户可以使用应用的视图。
managers/database_manager.hpp
包含 Device Sync 和数据库操作的重要定义。
managers/database_manager.cpp
实现一些 Device Sync 和数据库操作,例如创建列项、变更 Device Sync 查询订阅以及处理同步错误。
4

在不对代码进行任何更改的情况下,您应该能够在终端中运行该应用。运行应用程序时,将 atlasConfig.json 的路径作为参数传递:

./sync_todo /path-to-file/atlasConfig.json

运行应用,注册一个新用户帐户,然后将新事项添加到待办事项清单中。

提示

如需要,可展开终端窗口。

主屏幕顶部包含一排按钮和一个用于隐藏已完成任务的开关。如果终端窗口过小,按钮文本标签将不会显示。要查看这些标签,请放大终端窗口,FTXUI 会重新渲染内容以适应更大的窗口。

5

登录 Atlas App Services。在 Data Services 标签页中,单击 Browse Collections。在数据库列表中,找到并展开 todo 数据库,然后找到并展开 Item 集合。您应该会看到在此集合中创建的文档。

1

既然您已确认一切都按预期进行,您可以添加更改了。在本教程中,您将向每个Item添加一个priority属性,以便可以按其优先级筛选项目。

在生产应用程序中,您可以添加 PriorityLevel 枚举来限制可能的值。在本教程中,我们将使用数字属性来简化 UI 框架的使用。

为此,请按照以下步骤操作:

  1. 在首选 IDE 中打开客户端代码。

  2. state/ 目录中,ppen item.hpp 文件。

  3. 将以下属性添加到 Item 结构体:

    int64_t priority;
  4. 将新的 priority 属性添加到 REALM_SCHEMA() 中:

    REALM_SCHEMA(Item, _id, isComplete, summary, owner_id, priority)

    Item 模型现在应类似于:

    namespace realm {
    struct Item {
    realm::primary_key<realm::object_id> _id{realm::object_id::generate()};
    bool isComplete;
    std::string summary;
    std::string owner_id;
    int64_t priority;
    };
    REALM_SCHEMA(Item, _id, isComplete, summary, owner_id, priority)
    } // namespace realm
2
  1. state 目录中,转到 home_controller_state.hpp。在现有的 newTaskIsComplete 属性下添加新的 int 属性。然后,添加 static const int 来存储该属性的默认 int 值。

    HomeControllerState 结构现在可能类似于:

    struct HomeControllerState {
    static const int DEFAULT_TASK_PRIORITY = 3;
    // Used for creating a new task.
    std::string newTaskSummary;
    bool newTaskIsComplete{false};
    int newTaskPriority{DEFAULT_TASK_PRIORITY};
    ...more code here...
    };
  2. controllers 目录中,转到 home_controller.hpp。此控制器呈现应用的主视图。

    导入 string 和向量库:

    #include <string>
    #include <vector>

    为新的优先级 UI 元素创建字符串标签数组:

    std::vector<std::string> priorityLevelLabels = {
    "Severe", "High", "Medium", "Low"
    };
  3. 仍在 controllers 目录中,转到 home_controller.cpp。我们在此处添加 UI 元素,以便用户设置列项的优先级。FTXUI 提供了两个 UI 元素,适用于此功能:RadioboxDropdown。在本教程中,我们将使用 Dropdown,但如果您不喜欢 UI 在重排时以下拉列表的形式呈现,则可能更喜欢使用 Radiobox

    auto newTaskCompletionStatus 行之后添加这个新的用户界面元素输入:

    auto newTaskPriorityDropdown = ftxui::Dropdown(
    &priorityLevelLabels,
    &_homeControllerState.newTaskPriority
    );

    auto saveButton 函数闭包中,将任务优先级选择传递给 _dbManager.addNew() 函数调用:

    _dbManager.addNew(
    _homeControllerState.newTaskIsComplete,
    _homeControllerState.newTaskSummary,
    _homeControllerState.newTaskPriority);

    然后添加一行,将优先级选择重置为默认值:

    _homeControllerState.newTaskPriority = HomeControllerState::DEFAULT_TASK_PRIORITY;

    将下拉选择器添加到为项目行容器中的交互式元素设置布局的 auto newTaskLayout

    auto newTaskLayout = ftxui::Container::Horizontal(
    {inputNewTaskSummary, newTaskCompletionStatus, newTaskPriorityDropdown, saveButton});

    这部分代码现在应类似于:

    auto newTaskCompletionStatus = ftxui::Checkbox("Complete", &_homeControllerState.newTaskIsComplete);
    auto newTaskPriorityDropdown = ftxui::Dropdown(
    &priorityLevelLabels,
    &_homeControllerState.newTaskPriority);
    auto saveButton = ftxui::Button("Save", [this] {
    _dbManager.addNew(
    _homeControllerState.newTaskIsComplete,
    _homeControllerState.newTaskSummary,
    _homeControllerState.newTaskPriority);
    _homeControllerState.newTaskSummary = "";
    _homeControllerState.newTaskIsComplete = false;
    _homeControllerState.newTaskPriority = HomeControllerState::DEFAULT_TASK_PRIORITY;
    });
    auto newTaskLayout = ftxui::Container::Horizontal(
    {inputNewTaskSummary, newTaskCompletionStatus, newTaskPriorityDropdown, saveButton});
  4. 最后,在 home_controller.cpp 文件下文中,将新用户界面元素添加到 auto itemListRenderer

    inputNewTaskSummary->Render() | ftxui::flex,
    newTaskCompletionStatus->Render() | ftxui::center,
    newTaskPriorityDropdown->Render(),
    saveButton->Render(),

    这会在用户界面中的“保存”按钮之前显示新元素。

3
  1. managers 目录中,转到 database_manager.hpp。更新 addNew() 函数签名,使其包含我们从 home_controller.cpp 传入的 int newItemPriority

    void addNew(
    bool newItemIsComplete,
    std::string newItemSummary,
    int newItemPriority);
  2. 现在转到 database_manager.cpp,更新 addNew() 实现。在函数参数中添加 int newItemProperty

    void DatabaseManager::addNew(
    bool newItemIsComplete,
    std::string newItemSummary,
    int newItemPriority) {
    ...implementation...
    }

    在函数中添加一个新行,以便在将 Item 保存到数据库时设置 priority 属性的值:

    .priority = newItemPriority

    您的 addNew() 实现现在应该类似于:

    void DatabaseManager::addNew(bool newItemIsComplete, std::string newItemSummary, int newItemPriority) {
    auto item = realm::Item {
    .isComplete = newItemIsComplete,
    .summary = std::move(newItemSummary),
    .owner_id = _userId,
    .priority = newItemPriority
    };
    _database->write([&]{
    _database->add(std::move(item));
    });
    }
4

此时,再次构建并运行应用程序。在构建目录中,根据所作更改重建可执行文件:

cmake --build .

并运行应用程序:

./sync_todo /path-to-file/atlasConfig.json

使用本教程前面创建的帐户登录。您将看到先前创建的一个列项。添加新列项,您将看到现在可以设置优先级。为优先级选择 High 并保存条目。

现在切换回浏览器中的 Atlas 数据页面,并刷新 Item 集合。您现在应该会看到添加了 priority 字段并将其设置为1 的新项目。现有项目没有 priority 字段。

集合中的两个项目
点击放大

注意

为什么没有中断同步?

向 SDK 客户端对象添加属性不是一项破坏性变更 (breaking change),因此不需要重置客户端。模板应用已启用开发模式,因此对客户端对象的更改会自动反映在服务器端模式中。有关更多信息,请参阅开发模式更新数据模型

managers 目录中的 database_manager.cpp 文件中,我们创建了 Flexible Sync 订阅,其中定义了我们与用户设备和帐户同步的文档。默认情况下,我们订阅所有项目。您可以查看其他人创建的项目,但是服务器端规则阻止您对其进行写入。您可以在我们创建初始订阅的代码块中看到这一逻辑。如果应用程序打开时没有订阅,我们会为所有 Item 对象添加订阅:

_database->subscriptions().update([this](realm::mutable_sync_subscription_set& subs) {
// By default, we show all items.
if (!subs.find(_allItemSubscriptionName)) {
subs.add<realm::Item>(_allItemSubscriptionName);
}
}).get();

toggleSubscriptions()函数中,我们根据当前订阅状态切换订阅。 在用户界面中,用户可以在显示所有项目或仅显示自己的项目之间切换。 在此函数中,找到_myItemSubscriptionName逻辑。 如果尚无此订阅名称的订阅,则应用将添加对owner_id属性与经过身份验证的用户 ID 匹配的所有文档的订阅。

在本教程中,我们希望保持这一优先级,但同步标记为“高”或“严重”优先级的项目。

因此我们为 priority 属性使用 int64_t,并将用户界面中的优先级从最重要标记为最不重要。最高优先级(严重)的值为 0,最低优先级(低)的值为 3。我们可以对数字和优先级属性进行直接比较。

1

要更改订阅,请前往 managers 目录并打开 database_manager.cpp 文件。更新查询语句以包含优先级等于或小于 1 的文档。这应当仅包括“严重”(0)或“高”(1)优先级的项目。

if (!subs.find(_myItemSubscriptionName)) {
subs.add<realm::Item>(
_myItemSubscriptionName,
[&](auto &item){
return item.owner_id == _userId && item.priority <= 1;
}
);
}
2

再次运行应用程序。使用本教程前面创建的账号登录。在 Subscription 框中,按 Switch to Mine 按钮。在 SDK 最初重新同步文档集合后,您将只能看到创建的新的高优先级项目。您可能需要移动鼠标或使用箭头键使用户界面重新呈现已在后台同步的新项目。

您最初创建的事项文档未显示在设备上,因为它没有 priority 字段。如果希望此事项同步到设备,您可以在 Atlas 用户界面中编辑文档并为 priority 字段添加值。

提示

在启用开发者模式的情况下更改订阅

在本教程中,当您首次更改优先级字段的订阅和查询时,该字段将自动添加到 Device Sync Collection Queryable Fields 中。出现这种情况是因为模板应用默认启用了开发模式。如果未启用开发模式,您必须手动将该字段添加为可查询字段,以便在客户端同步查询中使用它。

有关更多信息,请参阅可查询字段

如果要进一步测试此功能,您可以创建各种优先级的事项。您将看到一个优先级较低的新事项短暂出现在事项清单中,继而又消失。同步错误处理程序提供了一条说明此行为的消息,十分有帮助:

A sync error occurred. Message:
"Client attempted a write that is not allowed; it has been reverted"

在这种情况下,SDK 会在本地创建该事项并将其与后端进行同步,然后撤销写入操作,因为它不符合订阅规则。

注意

已知用户界面问题

如果显示错误模式,并且您在关闭错误模式之前将鼠标移到终端中的项目列表上,那么用户界面呈现将中断。这与 FTXUI 库的限制有关。如果发生这种情况,请使用 ctrl + c 退出应用,然后重新运行应用。您可以通过在移动鼠标之前使用 Enter 键按错误模式中的 Dismiss 按钮来避免此问题。

向现有 SDK 对象添加属性是一项非破坏性变更 (breaking change),并且开发模式可确保模式变更反映在服务器端。

注意

分享反馈

怎么样?使用页面右下角的 Rate this page 小组件来评价其有效性。如果有任何问题,也可以在 Github 存储库 上提交问题。

来年

什么是 Atlas App Services?