在Android-Kotlin中使用SafeArgs共享数据
Android中的数据共享涉及在导航动作的片段之间传递参数。这不仅增强了相关目的地之间的通信,而且还建立了应用程序的连续流程。
简介
在过去的几年中,Android开发者利用Android Bundle类,这是在活动中共享数据的技术之一。
这有很多缺点,其中包括开发人员不得不做的繁琐工作,由于手动方法而导致的不可靠,以及缺乏类型安全,很容易使应用程序崩溃。
导航组件API(Jetpack库的一部分)是一种MAD(现代安卓开发)方法,通过引入SafeArgs ,解决了这些问题--这种插件允许你以更有效、更安全、更封装的方式传递数据。
前提条件
要完成本教程,你需要熟悉以下内容。
- [Android studio]的基本用法。
- [Kotlin编程]。
- Android开发中的强制范式概念。
- 视图绑定和/或数据绑定。
创建一个Android项目
启动Android Studio,用以下配置创建一个空的活动项目。

仔细检查以确保包的名称如所示。否则你将不得不配置你的项目,使其与本教程中使用的代码兼容。
启用viewBinding
视图绑定允许我们通过各自的绑定类和视图的ID访问XML文件中的视图。打开应用级build.gradle 文件,在android范围内粘贴以下内容,并同步项目。
android {
...
buildFeatures{
viewBinding true
}
}
创建两个片段
继续,我们需要至少两个片段,用来在导航时传递参数。右键点击项目的主包目录,创建两个空的片段,即FragmentA 和FragmentB 。
为了简单起见,我们把它们昵称为A和B。这两个应该有其相应的XML文件,即fragment_a.xml 和fragment_b.xml 。
初始代码设置
以下是我们将建立的初始代码。
i).FragmentA.kt
class FragmentA: Fragment() {
private var binding : FragmentABinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentABinding.inflate(layoutInflater)
return binding.apply {
// We'll write the rest logic here
}?.root
}
}
在这里,我们使用视图绑定来膨胀片段。
ii). Fragment_a.xml
<Button
android:id="@+id/btnShareData"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:text="Share data"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.9" />
这作为FragmentA的用户界面。上面的代码创建了一个按钮,以后当点击时将用于触发一个导航动作。
iii).FragmentB.kt
class FragmentB : Fragment() {
private var binding: FragmentBBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentBBinding.inflate(layoutInflater)
return binding.apply {
// We'll update the UI after receiving the arguments here
}?.root
}
}
这与FragmentA的目的类似。
iv).fragment_b.mxl
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Argument text" />
上述textView将用于显示在本教程后半部分讨论的成功到达后的数据😎。
创建导航图
导航图通常被称为nav-graph,它可以控制和可视化我们如何在片段之间进行操作。要创建导航图,在左侧面板上切换到资源管理,选择navigation 。
点击+ 按钮,创建一个名为my_nav 的新图。这将提示你自动添加相应的依赖关系。

点击OK ,你就可以开始了。
另外,你也可以在应用级build.gradle 文件中手动添加以下依赖关系。
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
在导航图中添加目的地
目的地是指包含在特定导航图中的屏幕或者说是片段。继续往前走,在我们刚刚创建的my_nav.xml 文件中粘贴以下代码。
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/my_nav"
app:startDestination="@id/fragmentA">
<fragment
android:id="@+id/fragmentA"
android:name="com.demo.safeargs.FragmentA"
android:label="Fragment A"
tools:layout="@layout/fragment_a">
<action
android:id="@+id/action_fragmentA_to_fragmentB"
app:destination="@id/fragmentB" />
</fragment>
<fragment
android:id="@+id/fragmentB"
android:name="com.demo.safeargs.FragmentB"
android:label="Fragment B"
tools:layout="@layout/fragment_b" />
</navigation>
这将添加两个目的地,将fragmentA 作为初始片段,并创建一个从A到B的导航动作。
为我们的导航图创建主机
现在我们已经创建了一个图,现在是时候设置活动了,它将作为我们应用程序的父级和入口点。这涉及到在活动的XML文件中添加一个导航主机片段,如下所示。
activity_main.xml
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/my_nav" />
注意:Android Studio会抱怨并建议你应该使用FragmentContainerView ,而不是片段标签。
MainActivity.kt
这是用导航图膨胀用户界面,并更新与当前目的地有关的工具栏。
class MainActivity : AppCompatActivity() {
private var binding: ActivityMainBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding!!.root)
setupActionBarWithNavController(findNavController(R.id.nav_host_fragment))
}
// Update action bar with the nav controller
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
设置SafeArgs
现在,我们已经准备好使用safe args进行飞行了,但在此之前,让我们先收拾好行李。
这涉及到加载所需的依赖性和插件。在项目级build.gradle 文件中添加以下classpath。
dependencies {
...
// build configurations that will be applied to all the modules in the project.
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
}
在你的模块级build.gradle 文件中,添加以下插件。
plugins {
...
// safe args plugin
id 'androidx.navigation.safeargs'
}
检查你是否启用了Java-8支持,因为大多数Gradle插件(包括afe-args)需要JDK 8。
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
建议在同步后完全重建和清理你的项目,以确保导航组件工具的生成。
SafeArgs支持哪些参数类型?
附在导航操作上的数据被称为参数。参数可以采取不同的类型,但不是所有的类型都被SafeArgs支持。
下面是一个支持的数据类型的列表。
预定义的类型。
- 布尔型
- 字符串
- 整数
- 浮点数
- 长型
自定义类型。
- 自定义可包裹
- 自定义可序列化
- 自定义枚举
- 资源参考
如何定义一个参数?
要给导航动作添加一个参数,在导航图预览中选择目标片段,然后在属性面板上点击+ 。将会弹出一个对话框,如下图所示。

这可以用来定义属性,如name,type,nullability, 以及适用的default value 。在上面的例子中,我们已经创建了 myAge它是一个 整数其默认值为 1.
通过点击Add ,下面的标签会自动添加。
<!-- inside fragment B tag -->
<argument
android:name="myAge"
app:argType="integer"
android:defaultValue="1" />
向参数传递值
想一想这样一个场景:你需要订购一个比萨饼送到你面前。你必须在送货前下订单。同样,当我们想从A 传递数据到B ,B 必须首先需要数据,也就是说,在B 中必须有一个参数,并且有一条连接A 和B 的路径。这个路径就是我们已经创建的导航动作。
继续,声明一个动作变量,并将19 赋值给它,例如。记住,值必须与参数的类型一致。在这种情况下,我们只能传递整数,否则,将抛出一个类型不匹配的异常。
在FragmentA.kt 文件中的onCreate() 方法中粘贴以下内容。
// trigger an action when the button is clicked
binding?.btnShareData?.setOnClickListener {
val action = FragmentADirections.actionFragmentAToFragmentB().setMyAge(20)
// navigate to FragmentB
findNavController().navigate(action)
}
在目的地接收参数
现在,我们的比萨饼运送正在进行中。我们需要准备在到达时接收它。
前往FragmentB.kt ,将代码更新为。
class FragmentB : Fragment() {
private var binding: FragmentBBinding? = null
private val args: FragmentBArgs by navArgs<FragmentBArgs>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentBBinding.inflate(layoutInflater)
return binding.apply {
val age = args.myAge.toString()
binding?.textView?.text = "My age is: $age"
}?.root
}
}
在这里,我们已经声明了一个变量args ,它接收与FragmentB相关的参数。我们还将textView的值设置为args 中的值。
同样地,我们也可以共享其他的数据类型。
共享自定义参数
除了预定义的数据类型外,SafeArgs允许我们传递我们想要的类型的对象。为了证明这一点,我们要用一个自定义的Parcelable参数来传递一个人的对象。
这样我们就可以在一个类型的罩子下分享不同的数据类型。添加以下插件,将Parcelize 到你的项目中。
plugins {
...
id 'kotlin-parcelize'
}
这个插件提供了一个Parcelable实现生成器,它可以自动为带有@Parcelize 注释的类生成parcels。这样的类必须扩展Parcelable ,这是一个Android特定的接口,我们在这里自己序列化对象。
创建一个数据类Person ,实现上述信息。
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class Person(
val name: String,
val age: Int
): Parcelable
上述类的一个对象将有两个属性,名字和年龄。
创建一个自定义的对象Argument
一个parcelable参数的创建方式与预定义类型相同,只是我们选择Parcelable类作为其类型。

另外,我们也可以在下面添加参数标签。
<!--Inside fragmentB tag (you can opt to replace the previous one)-->
<argument
android:name="person"
app:argType="com.demo.safeargs.Person" />
修改FragmentA.kt中的共享动作
首先,我们需要实例化Person类,然后在动作的参数中传递它。
binding?.btnShareData?.setOnClickListener {
// create a person object
val person = Person("Eric", 19)
val action = FragmentADirections.actionFragmentAToFragmentB(person)
findNavController().navigate(action)
}
在FragmentB.kt中接收包裹
与我们之前做的类似,我们将接收并在textView中显示参数的值。
...
return binding.apply {
val personParcel = args.person
binding?.textView?.text = "My Name is: ${personParcel.name}\nI\'m ${personParcel.age} years old."
}?.root
总结
在本教程中,我们已经了解到SafeArgs如何被用来在不同的目的地之间传递或共享数据。请注意,不建议传递大量的数据,因为参数的大小在Android中是有限的。