教程:使用 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 或更高版本。
从模板开始
本教程基于名为 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 模板应用。
探索模板应用
探索应用结构
花几分钟时间了解在 CMake 构建可执行文件时项目的组织方式。
在本教程中您不会直接使用这些文件,但它们包含演示如何使用 C++ SDK 的代码:
file | 用途 |
---|---|
controllers/app_controller.cpp | 使用 要进一步了解如何自定义应用配置,请参阅:连接到 Atlas App Services 后端。 此代码还设置 |
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 查询订阅以及处理同步错误。 |
检查后端
登录 Atlas App Services。在 Data Services 标签页中,单击 Browse Collections。在数据库列表中,找到并展开 todo 数据库,然后找到并展开 Item 集合。您应该会看到在此集合中创建的文档。
修改应用程序
添加新属性
向模型添加属性
既然您已确认一切都按预期进行,您可以添加更改了。在本教程中,您将向每个Item
添加一个priority
属性,以便可以按其优先级筛选项目。
在生产应用程序中,您可以添加 PriorityLevel
枚举来限制可能的值。在本教程中,我们将使用数字属性来简化 UI 框架的使用。
为此,请按照以下步骤操作:
在首选 IDE 中打开客户端代码。
在
state/
目录中,ppenitem.hpp
文件。将以下属性添加到
Item
结构体:int64_t priority; 将新的
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
在用户界面中添加元素以设置优先级
在
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... }; 在
controllers
目录中,转到home_controller.hpp
。此控制器呈现应用的主视图。导入 string 和向量库:
为新的优先级 UI 元素创建字符串标签数组:
std::vector<std::string> priorityLevelLabels = { "Severe", "High", "Medium", "Low" }; 仍在
controllers
目录中,转到home_controller.cpp
。我们在此处添加 UI 元素,以便用户设置列项的优先级。FTXUI 提供了两个 UI 元素,适用于此功能:Radiobox
或Dropdown
。在本教程中,我们将使用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}); 最后,在
home_controller.cpp
文件下文中,将新用户界面元素添加到auto itemListRenderer
:inputNewTaskSummary->Render() | ftxui::flex, newTaskCompletionStatus->Render() | ftxui::center, newTaskPriorityDropdown->Render(), saveButton->Render(), 这会在用户界面中的“保存”按钮之前显示新元素。
将新属性保存到数据库
在
managers
目录中,转到database_manager.hpp
。更新addNew()
函数签名,使其包含我们从home_controller.cpp
传入的int newItemPriority
:void addNew( bool newItemIsComplete, std::string newItemSummary, int newItemPriority); 现在转到
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)); }); }
更改订阅
在 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。我们可以对数字和优先级属性进行直接比较。
运行和测试
再次运行应用程序。使用本教程前面创建的账号登录。在 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),并且开发模式可确保模式变更反映在服务器端。
接下来的步骤
阅读我们的 C++ SDK 文档。
在 MongoDB 开发者中心查找面向开发者的博客文章和集成教程。
加入 MongoDB Community 论坛,向其他 MongoDB 开发者和技术专家学习。
探索工程和专家团队提供的示例项目。