Uber会员团队如何开发ActionCard设计模式以事半功倍(译文)
介绍 ActionCard 模式
ActionCard 模式将应用程序屏幕 UI、导航(路由)逻辑和其他应用程序逻辑简化为简单、分离的元素。UI 元素称为 卡片 ,关联的可重用逻辑元素称为 操作 。卡片和操作一起配置以创建应用程序屏幕和功能。每个屏幕都由服务器驱动的卡片数据模型提供支持。
此处描述的 ActionCard 模式实施是我们的团队利用优步工程方面的知识以及我们自己的大量试验和错误的结果。其结果是一种模式,使我们能够在多个屏幕和应用程序中快速推出新功能,并专注于快速迭代。
ActionCard 模式使我们能够降低复杂性并消除冗余。我们希望它可能对其他想要快速前进的团队有所帮助。 我们的出发点:拥有宏伟计划和有限资源的新团队
在 Uber 组建专门的会员工程团队之前,功能(Eats Pass 和 Uber Pass)由多个团队实施,这些团队也专注于其他多种产品。当我们发现一个重要的机会,可以通过提供更丰富的会员体验来为超过 1 亿的月度活跃用户提升 Uber 体验时,这一切都发生了变化。
我们新的专门会员团队的目标是将会员功能的相对简单的实现(仅由几个屏幕组成)扩展为一组丰富的功能,这些功能将集成到 Eats、Rides 和 Postmates 应用程序的用户体验中. 我们当前的产品相对简单,主要包括此处显示的屏幕:
图 1:旧会员功能屏幕 前面的路线图包括超过 16 个新的或重新设计的屏幕,如下所示:
图 2:未来的会员功能屏幕
当我们审查上面屏幕中的逻辑和布局并将它们与我们 12 个月的时间表进行比较时,我们的主要挑战立即很明显是构建许多具有独特布局的不同屏幕,并支持这些屏幕上的不同行为。
移动工程师已经花费太多精力编写新布局和集成业务逻辑。每个屏幕都需要一个新的实现。构建供用户在一系列屏幕之间导航的流程需要每个流程的独特实施。
我们的移动团队永远无法在我们计划的有限时间内交付所有这些,我们当然也没有时间进行 A/B 测试和迭代。我们有一个积极的路线图来增强 Uber 体验的几乎每个部分,目标是达到 1000 万会员。
我们只有 12 个月的时间来转变 Eats 和 Rides 应用程序的会员体验并实现这一目标。实现这一点的唯一方法是找到一种更有效的方法来提供所有这些功能。 我们的问题过多的冗余逻辑和过多的布局每个屏幕都有自己的布局逻辑;一些屏幕有多种布局变化,使布局约束更加复杂,并且需要在需要更改时进行额外测试 为每种类型的购买屏幕、续订、免费试用、过期后等重新整合了选择付款或进行购买等行为 导航到新流程以执行操作,然后导航返回所需的自定义逻辑以启动新屏幕,然后在用户完成操作后返回并刷新 我们的战略构建一组简单的可组合卡片和操作,以提供我们计划在未来 12 个月内推出的所有功能
我们着手看看是否可以通过执行以下操作将可重用性单位减少到一张卡片和一个动作: 保持一切简单并创建一组精细的卡片和操作 为每个屏幕不动作封装几乎所有的应用程序逻辑 仅对操作处理程序 (ActionFlows) 进行一次编码,并且能够从任何卡片启动它们 卡片一次编码,随处可见 通过简单地从后端为卡片和操作配置一组不同的模型来创建新的屏幕和流程 我们的计划将所有内容分解成卡片并将所有逻辑解耦为可重用的操作使用标准 UI 组件构建一组相对较小的卡片,这些卡片将提供我们团队所需的所有布局。 利用 Uber 的 RIBs 应用程序架构,轻松将路由逻辑与交互器分离到可以启动卡片屏幕的操作中。 提供一组默认的 ActionFlow,可以在任何地方处理大多数操作。在需要时允许动作"冒泡"到专门的动作处理程序。例如,只有在 Eats 或 Rides 应用程序上才有意义的操作将由专门的操作处理程序处理。 卡片和动作牌
卡片是具有单一用途的单个 UI 元素。这可以像显示图像或富文本一样基本。它们由 BaseUI 支持,还包含复杂的视图,如列表视图或消息视图。 卡片模型
所有卡模型都遵循相同的简单结构。它们包含卡片的 viewModel,通常会扩充单个已经存在的标准 UI 视图。以及当用户与卡片交互或点击卡片时可以执行的可选关联操作(某些卡片支持多个操作)。
示例卡模型:
动作
操作是当用户点击按钮或与 UI 交互时发生的事情。例如,导航到新屏幕、进行购买或更新首选项。 动作模型包含执行动作所需的所有数据。 动作可以配置为附加到任何屏幕上的任何卡片。 ActionFlow 是执行操作的应用程序逻辑。一些 ActionFlows 路由到新屏幕,其他 ActionFlows 处理发出 API 请求并使用响应更新状态。
图 3:动作模型 用卡片和动作组成一个屏幕卡片屏幕是通过卡片与相关操作的组合来配置的。我们的 API 提供的用于填充卡片屏幕的 CardScreenPresentation 模型如下:
图 4:ActionCard 屏幕
图 5:ActionCard 屏幕数据/布局结构
在内部,为了构建 CardScreen 配置,我们使用称为 DisplayConfig 的后端 GUI 工具。一旦在后端对动作和卡片模型进行编码,就可以使用 DisplayConfig 轻松地将它们排列或添加到新屏幕(我们实现 DisplayConfig 的更多细节是另一篇文章的主题)。 我们的成就我们没有构建 16 个独特的屏幕,而是仅实现了如下所示的 11 张卡片,并创建了大约 30 个独特的操作:
图 6:用于创建 16 个屏幕的完整卡片集 根据上面的卡片和相关操作,我们配置了以下 16 个屏幕,而不是实现了这些屏幕:
图 7:从卡片构建的会员屏幕
我们充分利用了 Uber 各个工程团队的综合经验。我们将我们的知识应用于解决基于真实用例的问题,并注重简单性。
其结果是 ActionCard 模式的强大实现,使我们能够支持跨多个应用程序和超过上述 16 个屏幕的强大功能集。使用 ActionCard 模式,我们能够启动复杂的流程,执行各种操作,并允许用户根据选择进行导航。
我们改进了这种模式,将可重复使用的元素从整个屏幕减少到单个卡片或操作。我们最大限度地 遵循了DRY原则,避免了编写无数行冗余代码。 布局仅编码在随处使用的卡片中 应用程序逻辑仅在操作处理程序中编码一次,并在需要时使用 所有屏幕都使用相同的渲染 RIB,此处描述为 CardScreenPresenter 操作可以启动流程或执行复杂的操作,例如购买或取消会员资格 可以仅使用操作在多级屏幕之间配置新的导航流程 构建它:实现 ActionCard 模式
ActionCard 模式从根本上来说很简单。它涉及呈现卡片屏幕和处理操作。卡片模型使 CardViewable 膨胀,动作模型提供 ActionFlow 所需的数据。ActionFlows 处理动作的执行,无论是启动新屏幕还是执行 API 并使用响应更新状态。 推介会渲染卡
ActionFlow 使用包含卡片模型和相关元数据的 CardScreenPresentation 对象更新 CardScreenPresentation。CardViewableProvider 从卡片模型中生成 CardViewable,并将它们返回给 CardScreenPresenter,CardScreenPresenter 会呈现它们。
图 8:渲染卡片屏幕的数据流 演讲的要素CardScreenPresentation(模型)
包含显示卡片屏幕所需的所有卡片(以及相关操作)和元数据的模型。分析事件将使用元数据将点击、展示和其他事件与此特定的卡片屏幕配置相关联。
CardScreenPresenter(视图)
这是用于渲染所有卡片屏幕的渲染器。我们当前的渲染器实现有两个 Ccrds 列表:一个从上向下滚动(主卡),另一个固定在底部并向上堆叠(底部固定卡)。以后可能需要多个不同的presenter;然而,这个单一的 CardScreenPresenter(如下)目前足以满足我们所有的用例。
图 9:卡片屏幕呈现器 卡片(型号)
每个卡片模型都包含呈现 CardViewable 所需的数据。它们还包含相关动作的动作模型。
卡片可见
CardViewable 是卡片的实际可渲染视图。
图 10:MessageCard 示例 CardViewableProvider
用于从卡片模型构建 CardViewable 的工厂。 处理动作动作(模型)
每个动作模型都有执行动作所需的数据。操作可以将用户导航到另一个屏幕,如 openHelp 或 openCheckout,或者是改变用户状态的专门操作,如 makePurchase 或 changeDefaultPaymentMethod。 动作流
ActionFlow 使用来自动作模型的数据来执行动作。ActionFlows 可以启动新屏幕或可以处理其他类型的操作,包括发出网络请求以进行购买。 ActionFlowProvider
ActionFlowProvider 为特定操作提供 ActionFlow。专门的处理程序处理针对特定上下文的操作,例如只能在特定应用程序上或在指定上下文中起作用的操作。 ActionFlows 打开购买屏幕并进行购买
图 11:进行购买 我们对卡片的了解一小套简单的卡片胜过一大套复杂的卡片
图 12:维护一小组易于重用的简单卡片
制作简单的卡片,然后将它们堆叠起来以创建更复杂的布局。这最终导致卡片数量减少。它大大减少了必须在 CardViewable 中维护的布局数量。它还实际上会 增加 可以创建的新屏幕的数量,而无需制作更多卡片。从简单的 RichTextCard 元素和 ImageCard 开始,然后根据需要添加基本的 UI 组件。为每张卡使用通用名称,这样当卡可用于多种不同用途时,您就不会描述特定的业务功能。 每张卡片使用一种布局
保持布局简单。在每张卡片中管理多个布局会使卡片的更改变慢,因为每次进行小的更改时都必须测试所有其他布局。我们的许多基本 UI 组件在内部处理各种布局,因此一张卡片可以支持相当多的布局,而无需向 CardViewable 本身添加任何布局逻辑。
CardViewables 可以根据卡片模型中可用的元素调整其呈现方式,但尽可能避免在布局逻辑中使用 if/else 逻辑或更糟糕的 switch 语句。如果事情变得复杂,只需制作一张新卡。从长远来看,这将使一切变得更加简单。 一切都利用 BaseUI
最好的布局是已经在您的应用程序的许多地方使用过的布局。尽可能在 CardViewable 中嵌入标准 UI 组件。 保持水平边距简单,使用垂直间隔卡
使用具有可配置高度和可选背景颜色的间隔卡来定义卡之间的空间。这消除了任何特殊的间距逻辑。 我们从行动中学到了什么动作处理应该存在于 ActionFlows 中
ActionFlow 是卡片框架中的操作处理程序。一旦编写完成,ActionFlow 就可以处理任何屏幕上的操作。ActionFlows 可以启动屏幕、更新屏幕状态以及进行购买或取消订阅等操作。虽然可以在其他地方添加操作处理,但最好使用 ActionFlow 处理所有操作。 确保将 Actions 与 UI 元素完全分离
任何操作都应该能够由任何 UI 元素处理。 CompletionActions 使 ActionCard 框架强大而动态
CompletionAction 是在另一个 ActionFlow 完成后执行的普通操作。CompletionActions 是 SuccessActions 或 FailureActions,它们通常会更新屏幕以显示新状态或在完成购买或取消订阅等操作后将用户导航到另一个位置。任何动作都可以有一个 CompletionAction。CompletionActions 链接在一起,可以在不添加额外代码的情况下引入复杂的流程和动态行为。
经常用作 CompletionActions 的动作: 向前导航到指定的卡片屏幕 返回 重新加载当前卡片屏幕 我们如何一劳永逸地解决分析问题使用 ActionCards 分析是准确和全面的
分析应完全集成到您的 ActionCard 实施中,以便默认捕获每一次展示、点击和用户旅程。将分析事件内置到框架中的一个好处是它们几乎总是准确和完整的,即使在测试新功能时您也可以依赖经过良好测试的分析逻辑。 后端应为每个屏幕提供一个分析标识符
水合屏幕的端点必须提供一个唯一标识符,该标识符可用于识别来自该特定卡片屏幕配置的印象和点击事件。 我们如何在不回调服务器的情况下使卡片屏幕响应用户交互示例:ActionCard 屏幕根据调查中的用户选择更改行为。
图 13:动态流程
上面的调查由 RadioOptionGroupCard 组成。无线电选项一起由一张卡片呈现,该卡片处理用户选择的动态更新状态。 根据用户选择更新继续按钮
Continue 按钮开始禁用,并在用户做出初始选择后变为启用。UpdateCards 操作通过更新按钮的卡片模型来处理切换"继续"按钮的启用状态。 UpdateCards 操作
UpdateCards 操作用于根据用户选择更新其他卡片的状态。UpdateCardsActionFlow 可以包含更新状态所需的任何复杂逻辑。在这里,我们使用一个简单的 ActionFlow,它只是将卡片模型替换为启用状态模型。
图 14:动态卡片更新 根据用户选择提交调查并导航到下一个屏幕
RadioOptionGroupCard 和 SubmitSurveyActionFlow 共享一个流。此流包含有关所选选项的数据和用户输入的文本。ActionFlow 提交调查并根据选项 ID 将用户导航到下一个屏幕。我们使用一个通用的 ActionCardData 流来促进这种类型的逻辑,以便它在任何需要的地方都可用。 我们如何构建动态流程使用 CompletionActions 取消会员资格并返回上一个屏幕
图 15:动态流第 1 部分
CompletionActions 在动作模型中被实现为 successAction 和 FailureAction。ActionFlows 在执行了指定的 Action 后完成。如果该操作打开了卡片屏幕,则在该屏幕消失时完成。
以下是 CancelMembershipAction 的示例,它同时具有 successAction 和 failureAction CompletionActions。
如果 CancelMembershipAction 成功,它将通过 NavigateBackAction 完成。NavigateBackAction 以 ReloadAction 完成。这是通过在 NavigateBackAction 中嵌套 ReloadAction 来实现的(可以在需要时使用这种方法导航其他级别)。
图 16:动态流程第 2 部分 – CancelMembershipAction->successAction(NavigateBackAction-> successAction(RefreshAction))
下图显示了 (1) 从 MangeMembership 屏幕导航到 EndMembershipScreen,(2) 实际取消会员资格,(3) 将用户返回到 ManageMembership 屏幕,以及 (4) 重新加载 EndMembership 屏幕以显示新取消的会员国。
图 17:完整的取消会员流程 为什么我们喜欢 ActionCard 模式没有中间层
ActionCards 是简单的本机 UI 元素,易于创建、维护和调试。没有复杂的后端依赖控制布局逻辑。没有中间层。一些框架通过将逻辑推到后端来解决布局或动态流的问题。我们通过简单地减少可重用性元素并将操作与 UI 完全解耦来解决了这个问题。 我们不再花太多时间编写 UI 布局
大多数新屏幕需要创建 1 张甚至零张新卡片。我们团队的工程师不再花费大量时间编写/维护布局。如果一个可以完美完成所有事情的布局引擎可用,它对我们的生产力影响很小,因为我们花在布局上的时间很少。 ActionCards 允许复杂的行为
ActionCards 为具有动态屏幕状态的可配置屏幕提供动力。它们允许启动新流程并在使用刷新屏幕完成任务后返回用户。他们这样做不需要编写新的移动代码。 结论
ActionCard 模式为我们团队的移动工程工作带来了极大的简单性。我们继续积极推出,现在能够专注于构建新功能的元素,这些元素实际上是新的。通常,这意味着只需创建一张卡片和操作即可启动新功能。我们能够仅通过更改配置进行 A/B 测试,并且迭代速度比以往任何时候都快。我们默认内置了强大的分析功能,因此我们可以轻松观察我们工作的影响。
我们希望我们在本文中分享的经验对其他希望在有限资源下更快发展的小型团队有所帮助。 致谢:
此处描述的所有内容都是 Uber Membership Mobile 团队的共同工作。以下团队成员在构建/完善此设计模式方面均发挥了重要作用: Aleksandr Nikiforov、Alok Sharma、Ameya Daphalapurkar、Andrew Paul Simmons、Dan Deng、Francisco Medina Bravo、Jessica Thrasher、Justin Muller、Omkar Sawant、Philip Donald、 Sam Hollingsworth、Sergey Evseev 和 李翔! 特别感谢 Zachary Thompson 在一开始就推动我们走得更远并实施最灵活和动态的框架版本,并感谢 Or Weizman 在这个雄心勃勃的项目中支持我们!我们真的跨 3 个移动应用程序重建了一切,如果没有他的支持,我们不可能完成这一切!
作者:
Andrew Paul Simmons
Andrew Simmons is a Mobile Product Engineer at Uber and is an architect of the ActionCard design pattern used by the Membership Team.
出处:https://www.uber.com/en-US/blog/developing-the-actioncard-design-pattern/