如何在Android-Kotlin中使用SafeArgs共享数据

155 阅读6分钟

在Android-Kotlin中使用SafeArgs共享数据

Android中的数据共享涉及在导航动作的片段之间传递参数。这不仅增强了相关目的地之间的通信,而且还建立了应用程序的连续流程。

简介

在过去的几年中,Android开发者利用Android Bundle类,这是在活动中共享数据的技术之一。

这有很多缺点,其中包括开发人员不得不做的繁琐工作,由于手动方法而导致的不可靠,以及缺乏类型安全,很容易使应用程序崩溃。

导航组件API(Jetpack库的一部分)是一种MAD(现代安卓开发)方法,通过引入SafeArgs ,解决了这些问题--这种插件允许你以更有效、更安全、更封装的方式传递数据。

前提条件

要完成本教程,你需要熟悉以下内容。

  • [Android studio]的基本用法。
  • [Kotlin编程]。
  • Android开发中的强制范式概念。
  • 视图绑定和/或数据绑定。

创建一个Android项目

启动Android Studio,用以下配置创建一个空的活动项目。

New project

仔细检查以确保包的名称如所示。否则你将不得不配置你的项目,使其与本教程中使用的代码兼容。

启用viewBinding

视图绑定允许我们通过各自的绑定类和视图的ID访问XML文件中的视图。打开应用级build.gradle 文件,在android范围内粘贴以下内容,并同步项目。

android {
    ...

     buildFeatures{
        viewBinding true
    }
}

创建两个片段

继续,我们需要至少两个片段,用来在导航时传递参数。右键点击项目的主包目录,创建两个空的片段,即FragmentAFragmentB

为了简单起见,我们把它们昵称为A和B。这两个应该有其相应的XML文件,即fragment_a.xmlfragment_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 的新图。这将提示你自动添加相应的依赖关系。

Accept dependencies

点击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支持。

下面是一个支持的数据类型的列表。

预定义的类型。

  • 布尔型
  • 字符串
  • 整数
  • 浮点数
  • 长型

自定义类型。

  • 自定义可包裹
  • 自定义可序列化
  • 自定义枚举
  • 资源参考

如何定义一个参数?

要给导航动作添加一个参数,在导航图预览中选择目标片段,然后在属性面板上点击+ 。将会弹出一个对话框,如下图所示。

Add integer argument

这可以用来定义属性,如name,type,nullability, 以及适用的default value 。在上面的例子中,我们已经创建了 myAge它是一个 整数其默认值为 1.

通过点击Add ,下面的标签会自动添加。

<!-- inside fragment B tag -->
<argument
    android:name="myAge"
    app:argType="integer"
    android:defaultValue="1" />

向参数传递值

想一想这样一个场景:你需要订购一个比萨饼送到你面前。你必须在送货前下订单。同样,当我们想从A 传递数据到BB 必须首先需要数据,也就是说,在B 中必须有一个参数,并且有一条连接AB 的路径。这个路径就是我们已经创建的导航动作。

继续,声明一个动作变量,并将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类作为其类型。

Add custom argument

另外,我们也可以在下面添加参数标签。

<!--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中是有限的。