乐趣区

关于android:在应用中导航时使用-SafeArgs-MAD-Skills

这是一个新的系列文章,咱们称之为 “Modern Android Development 技巧 ”,简称为 “MAD Skills”。本系列文章致力于帮忙开发者们打造更好的古代 Android 开发体验,敬请关注。

明天为大家公布本系列文章中的第三篇: 在利用中导航时应用 SafeArgs。如果您想回顾过去公布的内容,请参考上面链接查看:

  • 导航组件概览
  • 导航到对话框

这篇文章次要介绍 SafeArgs,它属于导航组件,并且能够在利用不同的目的地 (界面) 之间提供更加便捷的数据传递性能。

简介

当您在利用中导航到不同目的地的时候,可能会须要传递数据。为了防止应用全局对象援用,通过数据传递能够实现更好的代码封装构造,这样不同的 fragment 或者 activity 仅须要分享它们所需的数据即可。

导航组件能够通过 Bundles 传递数据,这个机制也可用于 Android 中跨 activity 传递数据。

这里咱们也能够应用同样的形式,为要传递的数据创立一个 Bundle,而后在接管侧将数据提取进去。

不过导航组件有更好的办法: SafeArgs。

SafeArgs 是一个 gradle 插件,它能够帮忙您在 导航图 中输出须要传递的数据信息。而后它会生成代码帮您解决创立 Bundle 时所需实现的简短的过程,并且在接管侧提取数据。

您也能够间接应用 Bundle,然而咱们倡议应用 SafeArgs。不仅仅是为了代码更简洁,更多的是它为数据减少了类型平安的保障,使得代码具备更好的健壮性。

为了向大家展现 SafeArgs 的成果,我将持续应用之前在 Dialog Destinations 演示过的 Donut Tracker (甜甜圈追踪) 利用。如果您心愿随着文章的解说进行同步操作,请下载 利用源码,并在 Android Studio 中关上。

制作甜甜圈的时候到了

咱们的 donut tracking 利用又来了:

Donut Track: 就是这个 App,它又来了

Donut Tracker 会显示甜甜圈的列表,每个列表项含有名称、形容和评分信息,这些内容有些是我增加的,有些是通过在点击 悬浮操作按钮 (FAB) 弹出的对话框中填写的。

点击悬浮操作按钮会弹出对话框填写新的甜甜圈信息

仅仅能够增加新的甜甜圈的信息是不够的,我还心愿能够批改已有甜甜圈的信息。没准我拿到一张甜甜圈的照片,或者我心愿晋升之前的评分。

比拟天然的实现办法是点击列表项,而后关上之前增加甜甜圈时的对话框,而后我能够在这里批改甜甜圈的信息。然而利用如何晓得对话框里显示哪个甜甜圈的信息呢?代码里须要传递所点击的列表项的信息。在这里,它须要将对应表项的 id 从列表所在的 fragment 传递到对话框所在的 fragment,而后对话框能够依据 id 从数据库里找到对应甜甜圈的信息,并且填充到表单里。

要传递 id,这里咱们应用 SafeArgs 来实现。

应用 SafeArgs

这里我须要阐明一下,我曾经实现了全副的代码,大家能够在 GitHub 的 示例 中找到残缺的代码。所以接下来我会给大家解说每个步骤,并且让大家看到示例代码的成果,而不是简简单单带着大家实现代码。

首先,我须要增加一些依赖库。

SafeArgs 和导航组件的其它模块不太一样,它自身并不是一个 API,而是一个能够生成代码的 gradle 插件。所以须要将它设置为 gradle 依赖,并且在构建时使其可能正确运行来生成所需的代码。

首先我在我的项目级的 build.gradle 文件的依赖局部中增加了上面的内容:

def nav_version = "2.3.0"
// 获取最新的版本号 https://developer.android.google.cn/jetpack/androidx/releases/navigation
classpath“androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version”

这里用到了 2.3.0 版本。如果您看到这篇文章的时候较晚,那么应该会有一个更新的版本供您应用。只有和您所应用的导航组件 API 的其它模块的版本统一就能够了。

而后我增加了上面的内容到 app 模块的 build.gradle 文件中。它使得在调用 SafeArgs 的时候能够生成所需的代码。

apply plugin: "androidx.navigation.safeargs.kotlin"

这里,gradle 提醒须要同步,所以我点击一下 “Sync Now”。

这是一个您不应该疏忽的提醒

接下来,在导航图中创立并传递所需的数据。

须要数据的指标界面是对话框 donutEntryDialogFragment,它须要晓得所需显示的对象的信息。点击指标界面会在右侧显示相干属性。

点击指标界面会显示该界面的属性列表,您能够在这里输出须要传递的数据

Arguments 窗格点击 + 能够增加数据,会弹出上面所示的对话框。这里我心愿传递的是所需显示的甜甜圈信息,所以数据类型设置为 Long,和数据库里的 id 的数据类型统一。

增加数据的时候会显示这个对话框,这里能够输出数据类型、默认值和其它所需的信息

须要留神的是当我定义数据类型为 Long 的时候,Nullable 的地位会变成灰色。这是因为 Java 编程语言中,根底数据类型 (IntegerBooleanFloatLong) 是基于原始数据类型 (intboolfloatlong) 进行封装的,而原始数据类型不可为空,所以咱们在应用根底数据类型的时候须要保证数据非空。

另外须要留神的是,利用当初应用该对话框增加新的元素 (我在上一篇文章 应用导航组件: 对话框目的地 | MAD Skills 中曾经介绍),同时也应用该对话框编辑已有元素。所以并不一定会传递元素 id,当用户创立新元素的时候,代码应该可能判断以后并无元素信息须要显示。所以我在对话框中 Default Value (默认值) 的地位输出了 -1,因为 -1 并不是一个无效的索引值。当代码导航至该界面并且没有数据传递的时候,-1 就会作为默认值传递,接收端的代码须要应用该值判断用户当初须要创立一个新的甜甜圈。

到这里,咱们执行 build 操作,gradle 就会针对所输出的数据生成相应的代码。这一点很重要,因为不是这样的话,Android Studio 就无奈晓得想要调用的函数在主动生成代码中的地位。

您能够在我的项目构造树的 “java(generated)” 分支下找到下面过程中生成的代码的执行后果。在子目录中,能够看到有新文件生成,它们负责传递和获取数据。

DonutListDirections 中,您能够找到 companion 对象,它是用于导航至对话框的 API。

companion object {
    fun actionDonutListToDonutEntryDialogFragment(itemId: Long = -1L): NavDirections =
        ActionDonutListToDonutEntryDialogFragment(itemId)
}

这里 navigate() 并没有应用最后的 Action,而是应用了 NavDirections 对象。它既封装了 action (咱们能够通过 action 导航至对话框),同时还封装了晚期创立的变量。

须要留神的是下面的 actionDonutListToDonutEntryDialogFragment() 函数须要一个 Long 类型的参数,咱们之前创立了相干变量,并且给它赋值为 -1。所以如果咱们在调用该函数的时候不加参数,该办法会返回一个 NavDirections 对象,并且它的 itemId 为 -1。

在另一个生成的文件 DonutEntryDialogFragmentArgs 中,您能够看到 fromBundle() 函数蕴含从指标对话框获取数据的代码:

fun fromBundle(bundle: Bundle): DonutEntryDialogFragmentArgs {
    // ...
    return DonutEntryDialogFragmentArgs(__itemId)
}

当初我能够利用生成的代码胜利传递和获取数据了。首先,我在 DonutEntryDialogFragment 类中编写代码来获取 itemId 数据,并且确定用户的用意是增加一个新的甜甜圈还是编辑一个已有的甜甜圈:

val args: DonutEntryDialogFragmentArgs by navArgs()
val editingState =
    if (args.itemId > 0) EditingState.EXISTING_DONUT
    else EditingState.NEW_DONUT

第一行代码用到了一个属性委托,它由 Navigation 组件库提供,这样写能够简化从 bundle 获取数据的过程。通过它能够在 args 变量中间接找到数据所对应的名称。

如果用户正在编辑一个已有的甜甜圈信息,那么这里的代码会获取该元素的信息,并且应用获取到的信息填充 UI:

if (editingState == EditingState.EXISTING_DONUT) {donutEntryViewModel.get(args.itemId).observe(
        viewLifecycleOwner,
        Observer { donutItem ->
            binding.name.setText(donutItem.name)
            binding.description.setText(donutItem.description)
            binding.ratingBar.rating = donutItem.rating.toFloat()
            donut = donutItem
        }
    )
}

须要留神的是这里的代码是从数据库申请信息,并且咱们心愿整个申请过程可能在 UI 线程之外进行。所以代码里会监听 ViewModel 所提供的 LiveData 对象,并且异步解决申请,当数据返回时填充视图。

当用户点击对话框里的 Done 按钮时,就须要存储用户所输出的信息了。上面这段代码会更新数据库里相应的数据,并且敞开对话框:

binding.doneButton.setOnClickListener {
    donutEntryViewModel.addData(
        donut?.id ?: 0,
        binding.name.text.toString(),
        binding.description.text.toString(),
        binding.ratingBar.rating.toInt())
    dismiss()}

下面的这些代码次要侧重于在目标界面里解决数据,当初咱们来看一下如何将数据发送到指标界面。

DonutList 中一共有两种路径能够转向对话框。其中一种是当用户点击 悬浮操作按钮 (FAB) 的时候:

binding.fab.setOnClickListener { fabView ->
    fabView.findNavController().navigate(DonutListDirections
        .actionDonutListToDonutEntryDialogFragment())
}

须要留神的是,这段代码里在创立 NavDirections 对象的时候调用了无参数的构造函数,所以变量会被默认赋值为 -1 (以表明这是一个新的甜甜圈),这也是咱们心愿通过点击悬浮操作按钮所要实现的成果。

另一个路径是当用户点击列表中已有元素的时候,会关上对话框。能够通过上面的 lambda 表达式实现,它将在 DonutListAdapter 的构建过程中传入 (即 onEdit 参数 ),而后会在每个表项的 onClick 被触发的时候被调用:

donut ->
    findNavController().navigate(DonutListDirections
        .actionDonutListToDonutEntryDialogFragment(donut.id))

这里的代码和用户点击悬浮操作按钮的代码类似,只不过这里将表项的 id 传了进去,通知对话框它要编辑一个已有的元素。而且和咱们之前的代码看到的一样,它会用已有元素的信息填充对话框,并且对该表项所做的批改也会相应更新数据库里的对应项。

总结

这就是 SafeArgs 的全部内容。应用起来非常简单 (比起 Bundle 要简略很多),因为依赖库会帮您生成代码来简化数据传递,并且保障了数据类型平安。通过这样的形式,您能够更好地利用数据封装,在目的地之间仅仅传递所需的数据而无需在更大的范畴内裸露数据。

请持续关注咱们后续的对于导航组件的内容,接下来咱们会介绍如何应用 Deep Link。

更多信息

更多对于导航组件的详情,请查看 导航组件应用入门文档

DonutTracker 利用的残缺代码,请查看 Github 示例:

更多古代 Android 开发技巧 (MAD Skills) 系列内容,请查看 Android Developers 频道

退出移动版