我们如何实现从Jetpack导航到战斗应用的导航。 Yandex食品报告

移动应用程序越来越多地使用深层链接。 这些链接使您不仅可以从外部访问应用程序,还可以访问特定的屏幕。 Yandex.Food的Android开发人员Vladislav Kozhushko解释了为什么我们从Jetpack实施导航以实现深层链接,遇到的问题,如何解决以及最终发生了什么。


大家好! 我叫弗拉德。 自2013年以来,我一直对Android开发感兴趣,从去年夏天开始,我就一直在Yandex.Ed工作。 我将向您介绍将导航组件库引入战斗应用程序的方式。

一切始于我们负有重构导航的技术任务这一事实。 然后产品经理来找我们,说我们将进行深层链接,其中会有很多,它们将导致不同的屏幕。

我们在这里认为-Google I / O 2018上展示的导航非常适合在深层链接上执行任务。 我们决定看看会发生什么。 我们使用Xcode的iOS同事拥有一个方便的图形编辑器,他们可以在其中使用鼠标来戳戳屏幕的整个布局,以及设置屏幕之间的过渡。 现在,我们也有了这样的机会,我们可以使用鼠标来设置过渡,到屏幕的深层链接以及将它们与片段相关联。 另外,我们可以在屏幕上设置参数。



也就是说,参数必须停留在UI编辑器中,或以XML编写。 除了在屏幕之间切换外,还会显示一个动作标签,其中我们指示其ID和需要进入的屏幕。



我们还指出了要用来打开屏幕的深层链接。 在这种情况下,有一个itemId参数,如果我们传递此类型的参数,它将导致一个片段,则此参数的值将传递给fragment参数,我们可以使用itemId键来获取和使用它。 该库还支持上行链路。 如果我们设置了上行链路,例如navdemo.ru/start/{itemId},那么我们将不再需要注册http / https方案来打开这种双链接。 图书馆将为我们做一切。

现在让我们讨论一下这些论点。 我们添加了两个参数,整数和布尔值,还给了它们默认值。



之后,组装片段时,我们将具有NextFragmentArgs类。 它有一个生成器,您可以用它来组装和设置所需的参数。 NextFragmentArgs类中也有吸气剂来获取参数。 您可以从包中收集NextFragmentArgs并转换包,这非常方便。



关于库的主要功能。 她有一个方便的UI编辑器,我们可以在其中进行所有导航。 我们得到的XML标记可读性与Views相同。 而且,当iOS开发人员在图形编辑器中更正某些内容时,我们没有那么痛苦,并且许多事情发生了变化。

深度链接可以与片段一起使用,也可以与活动一起使用,为此,您不需要在清单中注册大量的IntentFilter。 我们还支持上行链路,并且使用Android 6可以启用自动验证功能。 此外,在组装项目时,代码生成会使用所需画面的参数进行。 导航支持嵌套图和嵌套导航,这使您可以在逻辑上将整个导航分解为单独的子组件。



现在,我们将介绍图书馆,介绍我们经过的道路。 一切都从alpha版本3开始。我们实现了所有功能,将所有导航都替换为Navigation组件,一切都超级好,一切正常,diplink打开,但是出现了问题。



第一个问题是IllegalArgumentException。 它出现在两种情况下:图的不一致,因为图的表示形式与堆栈上的碎片不同步,因此有一个例外。 第二个问题是双击。 当我们第一次单击时,我们正在导航。 进入下一个屏幕,图形状态改变。 当我们第二次单击时,该图已经处于新状态,并且它正在尝试进行旧的过渡,该过渡已不存在,因此出现了这样的异常。 在此版本中,未打开diplink,其方案包含一个点,例如,project.company。 这是在库的将来版本中确定的,在稳定版本中,一切正常。

另外,不支持共享元素。 您可能已经看到了Google Play的工作方式:有一个应用程序列表,单击该应用程序,打开屏幕,然后出现移动图标的漂亮动画。 餐厅列表中的应用程序中也有此功能,但我们需要对共享元素的支持。 另外,SafeArgs不适用于我们,因此我们没有他们而生活。



修复深层链接很容易。 有必要用支持它的自己的方案替换库中的方案。 在反思的帮助下,我们敲响了课堂,改变了正则表达式的值,一切正常。



要纠正双击,我们使用了以下方法。 我们具有扩展功能,可以设置导航中的点击次数。 单击按钮或其他元素后,我们将更新ClickListener并进行导航以避免双重转换。 或者,如果您的项目中包含RxJava,我建议使用Jake Worton的RxBindingsgs库,并通过它使用我们可用的运算符以反应式处理View中的事件。



让我们谈谈共享元素。 由于它们稍后出现,因此我们决定完成导航器,并向其中添加导航。 我们是程序员,为什么不呢?



细化如下:我们从库中的导航器继承导航器。 这里并没有显示所有代码,但这是我们完成的主要部分。 我想指出的是,在进行导航之前,先检查FragmentManager的状态。 如果它被保存,那么我们将失去我们的团队。 我认为这是一个缺陷。

另外,当我们开始片段事务时,我们创建一个事务并设置所有需要翻阅的视图。 但是问题是,这个TransitionDestination是什么样的类? 这是我们的自定义类,可以在其中设置视图。 我们从Destination继承它并扩展功能。 我们设置了视图,并且目的地可以共享它们了。



下一部分-我们需要进行导航。 当我们单击按钮时,我们搜索ID目的地,然后将我们需要定位的图表拉出。 之后,我们将其转换为我们的“过渡目标”,其中包含“视图”。 接下来,我们将所有视图设置为动画过渡,并进行导航。 一切正常,一切都超级。 但是随后出现了alpha06。

这并不意味着我们确实在版本之间进行了跳转。 我们尝试根据需要更新库,但是也许这些是我们遇到的最基本的更改。

alpha06出现问题。 由于它是该库的Alpha版本,因此与重命名方法,回调,接口相关的不断变化,更不用说在方法中添加和删除参数的事实。 由于我们编写了自己的导航器,因此我们必须将库导航器代码与我们的导航器代码进行同步,以同时填充错误修复程序和新功能。

同样,在库本身中,当我们从早期的Alpha版本过渡到稳定版本时,其行为已更改,某些功能已被删除。 曾经有一个launchDocument标志,但从未使用过,然后将其删除。



例如,发生了一种变化,开发人员表示不赞成使用与DrawerLayout一起使用的navigationUp()方法,而使用另一个方法,在该方法中只需交换参数即可。





我们的下一个大迁移是在alpha11。 在这里修复了使用图形时导航的主要问题。 最后,我们卸下了掺杂的控制器,并使用了所有现成的东西。 安全参数仍然对我们不起作用,我们感到沮丧。

然后发布了beta01,在这个版本中,导航行为几乎没有改变,但是出现了以下问题:如果在应用程序中打开了一定数量的屏幕,那么我们必须在打开diplink之前清除堆栈。 这种行为不适合我们。 安全arg仍然无法正常工作。



我们在Google上写了一个问题,告诉我们所有规则都是最初构思的,而在代码本身中,这是因为在切换到Deeplink之前,我们使用了位于根目录中的图ID返回了根目录片段。 同样在setPopUpTo()方法中传递true标志,表示返回到此屏幕后,我们还需要将其从堆栈中删除。



我们决定退回掺杂的导航仪,并修复我们认为错误的地方。



这就是导致堆栈清除的原始问题。 我们解决了如下问题。 我们检查startDestination是否等于零(初始屏幕),然后将其用作图形的标识符作为标识符。 如果我们的startDestination ID不为零,那么我们将从图形中获取该ID,因此,我们无法清除堆栈并在我们拥有的内容之上打开DIP链接。 或者,作为一个选项,您可以简单地删除导航选项中的true弹出窗口。 从理论上讲,一切也应该起作用。

最后,一个稳定的版本问世。 我们很高兴,我们认为一切都很好,但是在稳定版本中,行为基本上没有改变。 他们只是将其定稿。 我们终于有了安全的args,因此我们开始在屏幕上积极添加参数,并在代码中的任何地方使用它们。 我们还发现导航不适用于DialogFragments。 由于有了DialogFragments,我们希望将它们全部转换为XML格式的图,并描述它们之间的过渡。 但是我们没有成功。



双开。 从第一个版本开始,我们就遇到了一个困扰-在应用程序冷启动期间两次打开Activity的问题。



它发生如下。 我们可以从代码中调用一个很棒的handle deeplink方法,例如,在活动中,我们获取onNewIntent()来拦截深层链接。 在这里,当使用Deeplink启动应用程序时,ACTIVITY_NEW_TASK标志变为Intent,因此发生以下情况:一个新的Activity启动,并且如果有当前Activity,它将被杀死。 因此,如果启动该应用程序,则白色屏幕首先启动,然后消失,出现另一个屏幕,它们看起来非常漂亮。

结果,引入该库,我们获得了以下优点。



我们拥有用于导航的文档以及屏幕之间转换的图形表示,如果有人在项目中找我们,他可以通过查看图形快速理解这一点,并在Studio中打开演示文稿。

我们有SingleActivity。 所有的屏幕都是在片段上制作的,所有的链接都导致片段,我认为这很方便。

结果是将Deeplink与片段进行了简单的链接,只需将deeplink标记添加到片段中,库就可以为我们做所有事情。 我们还将导航分为嵌套的子图,进行嵌套导航。 这些是不同的东西。 实际上,它只是一个嵌套图,它包含在图内,而嵌套导航是指使用单独的导航器在屏幕上四处走动时。

我们还可以在代码中动态更改图形,可以添加顶点,可以删除顶点,可以更改开始屏幕-一切正常。

我们几乎忘记了如何使用FragmentManager,因为使用它的所有逻辑都封装在库中,而库为我们做了所有不可思议的事情。 该库还可以与DrawerLayout一起使用,如果您设置了根片段,则库本身将在其上绘制一个汉堡包,并且当进入下一个屏幕时,它将绘制一个箭头并在从倒数第二个片段返回时以动画方式进行显示。

我们还将所有参数(大多数参数)都转移到SafeArgs,并且在构建项目时会创建所有参数。 此外,我们解决了困扰我们的所有问题,并修改了图书馆以满足我们的需求。

SafeArgs可以生成Kotlin代码,为此使用了单独的插件。



另外,该库也有缺点。 首先是堆栈的清理已交付到稳定版本。 我不知道为什么要这么做,也许对某人来说这么方便,但是对于我们的应用程序,我们想在已有内容的基础上打开深层链接。

片段本身是由导航器片段创建的,并且是通过反射创建的。 我认为这不是实施中的优势。 不支持Deeplink的条件。 您的应用程序可能具有仅对授权用户可用的秘密屏幕。 为了按条件打开深层链接,您需要为此编写拐杖,因为您无法设置深层链接处理器来控制我们并说出要做什么,要么通过深层链接打开屏幕,要么打开另一个屏幕。

同样,转换命令也丢失了,这都是因为导航器的片段检查状态是否被保存,如果被保存,则我们什么都不做。

我们还必须使用文件来修改库,这不是一个加法。 另一个重大缺点-我们没有机会在Deeplink前面打开一系列屏幕。 对于我们的应用程序,我们想与购物篮建立深层链接,在此之前,我们打开所有之前的屏幕:餐厅,特定餐厅以及仅在购物篮之后。

另外,要使用导航,您必须具有View实例。 要获得导航器,您需要转到视图-导航库本身将与该视图的父级联系-并尝试在其中找到导航器。 如果她找到了,则屏幕转到我们需要的屏幕。

但是问题来了:值得在战斗中使用图书馆吗? 如果出现以下情况,我会说“是”:



如果我们需要快速的结果。 该库的实现非常迅速,可以在大约20分钟内将其插入到项目中,所有转换都可以用鼠标戳一下,这一切都很方便。 它还可以用XML快速编写,快速组装和快速运行。 一切都超级好。

如果您需要在应用程序中有许多使用深层链接的屏幕,并且没有条件可以打开它们。

没有任何活动。 在这里,我的意思不仅是单个活动。 我们既可以在具有一个“活动”的片段上进行导航,也可以在片段和“活动”的混合物上进行导航,仅在图中还可以描述活动。 为此,在导航器内部使用了提供程序,其中有两个导航实现。 一个是在Activity上创建的,该Intent创建了转到所需Activity的Intent,第二个实现是一个片段导航器,该片段导航器在内部与片段管理器一起工作。

如果您没有在屏幕之间切换的复杂逻辑。 每个应用程序都有自己独特的方式,如果打开屏幕有复杂的逻辑,则此库不适合您。

您已经准备好像我们一样进入源文件并对其进行修改。 这实际上非常有趣。



如果应用程序具有复杂的导航逻辑,是否需要在深度链接之前打开一系列屏幕,或者是否需要困难的条件才能通过深度链接打开屏幕,我将拒绝。 原则上,可以使用授权示例完成此操作,只需打开所需的屏幕,然后在其上方是授权屏幕。 如果用户未登录,则将您转至堆栈上的上一个屏幕,在此之前打开了秘密屏幕。 这样的决定。

失去球队对您至关重要。 相同的Cicerone可以将命令保存到缓冲区,并在导航器可用时执行命令。

如果您不希望完成代码,那么这并不意味着您作为开发人员就不想做任何事情。 假设您有最后期限,产品经理告诉您您需要削减功能,而一家企业则希望推出功能。 您需要一个开箱即用的交钥匙解决方案。 导航组件与这种情况无关。

还有一件重要的事情-不支持DialogFragments。 可以添加一个可以使用它们的导航器,但是由于某些原因,没有添加它们。 问题在于它们本身以普通片段的形式打开。 isShowing的复选框未设置,因此,不执行用于创建对话框的DialogFragments生命周期。 实际上,DialogFragment作为常规片段打开,整个屏幕没有创建窗口。

这就是我的全部。 挖掘源代码,这确实很有趣且令人兴奋。 对于那些对使用导航库感兴趣的人,这里是有用的材料 。 谢谢您的关注。

Source: https://habr.com/ru/post/zh-CN455479/


All Articles