xamarin.forms_如何使用委托和协调器实现Xamarin.Forms导航

xamarin.forms

by Rafael Fiol

通过拉斐尔·菲奥尔(Rafael Fiol)

如何使用委托和协调器实现Xamarin.Forms导航 (How to implement Xamarin.Forms navigation using delegates and coordinators)

Recently I have been thinking a lot about how to best implement page navigation within a Xamarin.Forms mobile app. My deep-dive into this topic started when a colleague sent me an article entitled The Coordinator, written by Soroush Khanlou. Initially, I was a very happy passenger on the Coordinator bandwagon. I guess I still am. It’s a wonderful way to think about and implement separation of concerns with respect to UI/UX and general navigation.

最近,我一直在思考如何在Xamarin.Forms移动应用程序中最好地实现页面导航。 当我的同事给我发了一篇由Soroush Khanlou撰写的题为The Coordinator的文章时,我就开始深入研究这个主题。 最初,我是Coordinator潮流中的一个非常高兴的乘客。 我想我还是。 这是思考和实现关注点分离的绝佳方法 关于UI / UX和常规导航。

The following notes represent my thoughts on the subject, learnings with regard to an implementation in Xamarin.Forms, and how delegates in C# can help.

以下注意事项代表我关于这个问题的想法,学习收获关于一个实施Xamarin.Forms,并在C#中的代表如何能帮助。

为什么要协调员? (Why Coordinators?)

Soroush’s solution focuses primarily on the problems with overstuffed view controllers (citing the iOS UIViewController as a primary example), and suggests that a Coordinator’s primary responsibility should be to take over navigation and model mutation. He states that:

Soroush的解决方案主要针对视图控制器过多的问题(以iOS UIViewController为例),并建议协调器的主要职责是接管导航和模型变更。 他指出:

When we take those tasks out of a view controller, we end up with a view controller that’s inert. It can be presented, it can fetch data, transform it for presentation, display it, but it crucially can’t alter it.

当我们将这些任务从视图控制器中取出时,最终得到的是惰性的视图控制器。 它可以呈现,可以获取数据,对其进行转换以进行呈现,显示,但至关重要的是无法对其进行更改。

This is good. This is what we want. It means that we can reuse our view controllers as participants in multiple workflows, with purpose-built Coordinators for each workflow. Our Pages and View Models — in our MVVM applications — will no longer have messy navigation concerns.

很好 这就是我们想要的。 这意味着我们可以将视图控制器用作多个工作流的参与者,并为每个工作流使用专门设计的协调器。 我们的MVVM应用程序中的页面和视图模型将不再具有混乱的导航问题。

Consider this common scenario. Let’s imagine that your app includes a login page. After a user completes the login page, your app then displays a dashboard page. Then, in a later design incarnation of your app, you decide that not all pages require a login. What if you want to reuse that login page elsewhere, and go to a different post-login page depending on the workflow that triggers the login?

考虑这种常见情况。 假设您的应用程序包含一个登录页面。 用户完成登录页面后,您的应用程序将显示一个仪表板页面。 然后,在以后设计应用程序时,您决定并非所有页面都需要登录。 如果要在其他地方重用该登录页面,并根据触发登录的工作流程转到另一个登录后页面怎么办?

The obvious (and ugly) solution is to start littering your Pages or View Models with conditional logic — normally in the form of IF/THEN statements. That never scales, and in large applications you end up with very brittle code.

一个显而易见的(丑陋的)解决方案是开始使用条件逻辑(通常以IF / THEN语句的形式)乱乱您的Pages或View模型。 它永远无法扩展,在大型应用程序中,您最终会得到非常脆弱的代码。

救援协调员。 (Coordinators to the Rescue.)

When I began my experiments to implement Coordinators in Xamarin.Forms, I had several design goals in mind.

当我开始尝试在Xamarin.Forms中实现协调器的实验时,我想到了几个设计目标。

First, this is not about MVVM. I have no desire to implement yet another MVVM framework for Xamarin. Further, navigation is not an MVVM concern. I assert that View Models (and for that matter, Pages) should not be aware of other View Models (and pages) within an application.

首先,这与MVVM无关。 我不想为Xamarin实现另一个MVVM框架。 此外,导航不是MVVM的问题。 我断言,视图模型(就此而言,页面)应该不知道应用程序中的其他视图模型(和页面)。

Second, Coordinators should not know anything about the underlying View Model that supports a page. My desire is for a Coordinator to simply launch pages, and respond to hooks exposed by those pages. This is somewhat analogous to the idea of webhooks (more on this in a bit).

其次,协调员对支持页面的基础视图模型一无所知。 我希望协调员可以简单地启动页面,并响应那些页面暴露的钩子 。 这有点类似于webhooks的想法( 稍后对此进行详细介绍)。

Third, I wanted the implementation to be straightforward, without a lot of coercing of objects, and without diminishing any of the features that make RAD with Xamarin.Forms so amazing.

第三,我希望实现的过程是直接的,没有太多的对象强制,并且没有减少使RAD with Xamarin.Forms如此惊人的任何功能。

营救代表。 (Delegates to the Rescue.)

Before getting into building Coordinators, we have to first address a fundamental issue that comes up with every MVVM project. Specifically, how does a View Model, which is handling UI interactions, signal to the workflow that it has completed its intended task?

在建立协调器之前,我们必须首先解决每个MVVM项目所涉及的基本问题。 具体来说,用于处理UI交互的视图模型如何向工作流发出已完成其预期任务的信号?

Using the login example I cited earlier, how does the View Model signal that the user logged in successfully (or unsuccessfully), and pass the generated access token and user information to the workflow that instantiated the page? And more importantly, how does the View Model publish that information? Is there a contract that can be inspected without having to dig through reams of code?

使用我之前引用的登录示例,视图模型如何发信号通知用户成功(或失败)登录,并将生成的访问令牌和用户信息传递给实例化页面的工作流? 更重要的是,视图模型如何发布该信息? 是否存在可以无需查看大量代码即可检查的合同?

For me, the solution was to use delegates. In C#, a delegate is a reference type variable that holds a reference to a method. Delegates are similar to function pointers in C++. However, delegates are type-safe and secure. Using delegates, I create hooks, that the caller can use to participate in and influence the workflow.

对我来说,解决方案是使用委托 。 在C#中, 委托是保存对方法的引用的引用类型变量。 委托类似于C ++中的函数指针。 但是,委托是类型安全的。 使用委托,我创建了hooks ,调用者可以使用它们来参与和影响工作流程。

For example, in my LoginViewModel, I declare a delegate definition, as:

例如,在我的LoginViewModel中,我将委托定义声明为:

I added this in the namespace, just above the actual declaration of my LoginViewModel class. This defines the pattern for the delegate, not the actual implementation. It tells a programmer that this View Model will perform a callback when the login is completed, and defines what parameters will be passed. In my LoginViewModel, I expose a property of this type. Users of this View Model can attach to it, to get a callback when the login completes.

我将此添加到了我的LoginViewModel类的实际声明上方的名称空间中。 这定义了委托的模式,而不是实际的实现。 它告诉程序员,该View模型将在登录完成后执行回调,并定义将传递哪些参数。 在我的LoginViewModel中,我公开了此类型的属性。 该视图模型的用户可以附加到该模型,以便在登录完成后获得回调。

In the LoginViewModel, there is no implementation of this delegate. Instead, the View Model’s caller will implement the method — usually as an anonymous method or lambda — creating a sort of callback (or webhook) pattern.

在LoginViewModel中,没有此委托的实现。 取而代之的是,View Model的调用者将实现该方法(通常作为匿名方法或lambda),从而创建一种回调(或webhook )模式。

Also in the LoginViewModel is an implementation of an ICommand which is invoked when the login button is pressed. It is in this command, after the login is successful, that we invoke the delegate. Below is a snippet of the command implementation. I’ve omitted much of the boilerplate code related to exception handling, etc.

同样在LoginViewModel中是ICommand的实现,当按下登录按钮时将调用该ICommand 。 登录成功后,正是在此命令中调用委托。 以下是命令实现的代码段。 我已经省略了很多与异常处理等相关的样板代码。

Notice the last line, which invokes the delegate. Also notice the null conditional. The delegate is only invoked if it exists. So let’s see how we can use this.

注意最后一行,它将调用委托。 还要注意null条件 。 仅在存在委托时调用该委托。 因此,让我们看看如何使用它。

穿钩。 (Threading the Hook.)

We’re not quite ready to create our Coordinators yet. But we have solved for a major design goal. We’ve defined a strategy for our View Models to expose important lifecycle events to anyone who cares. Continuing with the LoginViewModel example, we can hook into this as follows:

我们还没有准备好创建我们的协调员。 但是我们已经解决了主要的设计目标。 我们已经为我们的视图模型定义了一种策略,可以将重要的生命周期事件公开给任何关心的人。 继续LoginViewModel示例,我们可以如下所示进行连接:

Simple. The above demonstrates how we can create an anonymous method that will be invoked by our View Model. The method will receive the access token and user object, so that we can do something important with it, such as storing that data and navigating to the next page, for example. This is where our Coordinators come in. They will use these hooks to do something important.

简单。 上面的示例演示了如何创建将由我们的视图模型调用的匿名方法。 该方法将接收访问令牌和用户对象,以便我们可以对它做一些重要的事情,例如存储数据并导航到下一页。 这是我们的协调员进来的地方。他们将使用这些钩子来做重要的事情。

那么代码隐藏页面呢? (What About the Code-behind Pages?)

Earlier I said that we’ve defined a strategy for our View Models to expose important lifecycle events to anyone who cares. The question is: exactly who cares when the login completes? Well, our as-of-yet-undefined Coordinates will care. But, recall that my second design goal stated:

之前我曾说过, 我们已经为我们的视图模型定义了一种策略,可以将重要的生命周期事件公开给任何关心的人 。 问题是:究竟谁在乎登录何时完成? 好吧,我们尚未定义的坐标会在意。 但是,回想一下我的第二个设计目标:

Coordinators should not know anything about the underlying View Model that supports a page.
协调员应该对支持页面的基础视图模型一无所知。

I’ve made a decision that this is to be a hard-and-fast rule. My Coordinators know about the pages that make up a workflow, but do not know or interact with the View Models of those pages. In fact, my code-behind pages create their View Models as protected properties. So, to keep the contracts clean, my code-behind pages simply relay delegate invocations. For example, my LoginPage code-behind looks like this:

我已经决定这将是一条严格的规则。 我的协调员知道组成工作流程的页面,但不知道这些页面的视图模型或与之交互。 实际上,我的代码隐藏页面将其视图模型创建为受保护的属性。 因此,为了保持合同的整洁,我的代码隐藏页面仅中继了委托调用。 例如,我的LoginPage背后的代码如下所示:

Notice that the code-behind page registers itself as the object interested in a callback from the View Model (line 12), and then — because the code-behind does not concern itself in matters of navigation — it simply relays the callback to its own delegate (line 13).

请注意,代码隐藏页面将自己注册为对视图模型的回调感兴趣的对象(第12行),然后-由于代码隐藏本身与导航无关,它只是将回调中继给自己代表(第13行)。

This might seem redundant, but it has practical applications. First, it means that the View Model can expose delegate methods that are private to the code-behind page. Second, it means that there is very loose coupling between the Coordinators and pages.

这似乎是多余的,但是有实际应用。 首先,这意味着视图模型可以公开代码隐藏页面专用的委托方法。 其次,这意味着协调器和页面之间的耦合非常松散。

Xamarin协调器实现 (A Xamarin Coordinator Implementation)

With this, and my third design goal in mind, which is…

考虑到这一点,我的第三个设计目标就是……

I wanted the implementation to be straightforward, without a lot of coercing of objects, and without diminishing any of the features that make RAD with Xamarin.Forms so amazing.
我希望实现的实现是简单明了的,没有太多的对象强制,也没有减少使RAD with Xamarin.Forms如此惊人的任何功能。

…I decided that pages can live on their own, or they can have attached Coordinators. It took me a while to arrive at this decision. I actually first started with a Coordinator-first design approach, whereby everything was driven by a Coordinator. I quickly found that this was terribly limiting and super complicated. It required a complex push/pop pseudo-navigation manager, and it limited my ability to easily use TabbedPages, MasterDetailPages, and modals. I also found myself pushing navigation logic into View Models. I didn’t like it.

…我决定页面可以独立存在,也可以附加协调员。 我花了一段时间才做出这个决定。 实际上,我最初是从协调器优先的设计方法开始的,该方法由协调器来驱动。 我很快发现这是非常有限的,而且非常复杂。 它需要一个复杂的push / pop伪导航管理器,并且限制了我轻松使用TabbedPages,MasterDetailPages和模式的能力。 我还发现自己将导航逻辑推入了视图模型。 我不喜欢

So instead, I opted for a Page-first approach, whereby Pages can have a Coordinator attached to them. This solves a big issue related to garbage collection, since the Xamarin.Forms framework already handles retaining and disposing of Pages based on visibility lifecycles. Had I gone down the Coordinator-first approach, I would have had to add a bunch of ugly logic to manage the stack myself.

因此,我选择了“页面优先”方法,即可以在页面上附加协调器。 这解决了一个与垃圾回收有关的大问题,因为Xamarin.Forms框架已经根据可见性生命周期处理了Pages的保留和处置。 如果我不采用协调器优先的方法,我将不得不添加一堆难看的逻辑来自己管理堆栈。

With the Page-first approach, you can create a Page as you normally do, and you can pass (attach) a coordinator via the page constructor. So for the login page, it looks something like this:

使用“页面优先”方法,您可以像通常一样创建页面,并且可以通过页面构造函数传递(附加)协调器。 因此,对于登录页面,它看起来像这样:

The nice thing about this approach is that I can also inject a Coordinator in XAML. This is especially useful in MasterDetailPage or TabbedPage situations, wherein you don’t normally create the instances in code. Such as:

关于这种方法的好处是,我还可以在XAML中注入协调器。 这在MasterDetailPage或TabbedPage情况下尤其有用,在这种情况下,通常不会在代码中创建实例。 如:

Before we get into the mechanics, let’s take a look the implementation of LoginCoordinator. Specifically, we’ll focus on what responsibilities this coordinator takes on, which is all encapsulated within the Coordinator’s Start() method. This is where all of the magic — navigation logic — happens.

在开始讲究技巧之前,让我们看一下LoginCoordinator的实现。 具体来说,我们将专注于此协调器承担的职责,这些职责都封装在协调器的Start()方法中。 这就是所有魔术(导航逻辑)发生的地方。

Before we go further, let me briefly explain a portion of the business logic of the app. In this app (built for musicians), each user can belong to one or more bands. So after authenticating (logging in), the user is presented with a list of their bands. The user then selects one, and the app then completes the login workflow, and shows the post-login master detail page.

在继续之前,让我简要解释一下应用程序的部分业务逻辑 。 在此应用程序(专为音乐家打造)中,每个用户可以属于一个或多个乐队。 因此,在进行身份验证(登录)后,会向用户显示其乐队列表。 然后,用户选择一个,然后该应用程序完成登录工作流程,并显示登录后的主详细信息页面。

So, this LoginCoordinator actually orchestrates the presentation of two separate pages — the LoginPage and the BandPickerPage. Each of the pages can be used independently or as part of other workflows.

因此,该LoginCoordinator实际上协调了两个单独页面的显示-LoginPage和BandPickerPage。 每个页面都可以独立使用,也可以作为其他工作流程的一部分使用。

For example, the BandPicker page is used in another part of the app when the user wants to switch between their active bands, without having to login again. The BandPicker page is completely unaware of how it is being used. It just has to focus on doing what it does — pick bands.

例如,当用户想要在其活动频段之间切换而不必再次登录时,BandPicker页面将在应用程序的另一部分中使用。 BandPicker页面完全不知道其使用方式。 它只需要专注于做自己的工作-挑选乐队。

框架的东西 (The Framework Stuff)

The implementation of the Coordinator itself is pretty easy. In my approach, there is one interface (called ICoordinator) and an abstract base class that partially implements that interface. The interface looks like this:

协调器本身的实现非常容易。 在我的方法中,有一个接口(称为ICoordinator)和部分实现该接口的抽象基类。 该界面如下所示:

Each coordinator has to only implement the Start() method. The other two methods — AttachToPage() and DetachFromPage() — are implemented in the abstract base class. Here’s what that looks like:

每个协调器仅必须实现Start()方法。 另外两个方法-AttachToPage()和DetachFromPage()-在抽象基类中实现。 看起来像这样:

The application’s coordinators simply need to extend this base class and override the Start() method. That’s pretty much it. There’s only one other piece to the framework, which is a simple base class for ContentPage subclasses. It does nothing more than call the AttachToPage() and DetatchFromPage() methods of the Coordinators passed in to the constructor. That’s it.

应用程序的协调器只需要扩展此基类并重写Start()方法。 就是这样。 框架只有另外一部分,它是ContentPage子类的简单基类。 它仅执行调用传递给构造函数的协调器的AttachToPage()和DetatchFromPage()方法。 而已。

摘要 (Summary)

Many thanks to Soroush Khanlou for the inspiration. I’d love to hear about how you’re using Coordinators in your own Xamarin projects, and any ideas you might have to improve upon the implementation I’ve presented here.

非常感谢Soroush Khanlou的启发。 我很想听听您如何在自己的Xamarin项目中使用协调器,以及在我在此处介绍的实现上可能需要改进的任何想法。

You can download my sample app from GitHub.

您可以从GitHub 下载我的示例应用程序。

翻译自: https://www.freecodecamp.org/news/xamarin-forms-navigation-using-delegates-and-coordinator-a01fb7e3c120/

xamarin.forms

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值