安卓中导航组件中的安全参数如何实现类型安全?

huangapple go评论108阅读模式
英文:

How is safe args in navigation component type safe (Android)?

问题

在正常的片段事务中,我们会将数据传递如下:

Fragment fragment = new Fragment();
Bundle bundle = new Bundle();
bundle.putInt(key, value);
fragment.setArguments(bundle);

这也是类型安全的吗?
那么当我们说安全参数是类型安全时,这意味着什么?
当我们说安全参数是类型安全时,确切地说类型安全是什么意思?

提前感谢!

英文:

In a normal fragment transaction we would pass data as:

Fragment fragment = new Fragment();
Bundle bundle = new Bundle();
bundle.putInt(key, value);
fragment.setArguments(bundle);

Isn't this type safe too?
So what does it mean when we say that safe args are type safe?
What exactly does type safety mean when we say that safe args are type safe?

Thanks in advance!

答案1

得分: 2

Sure, here is the translated text:

是的,您提供了在片段之间传递参数的常规方式。这是类型安全的,因为 Bundle 类提供了用于存储和获取不同类型数据的 API。这意味着您不会遇到使用它时的 ClassCastException(详细解释见下文)。

> 那么当我们说安全参数是类型安全时,这意味着什么?

我想您在谈论 安全参数,它是一种为 Android 提供了一种类型安全且易于使用的机制,用于在 Android 导航组件中传递数据的 Gradle 插件

使用 Safe Args,您可以在 XML 文件中为每个目标定义参数,插件会为每个目标生成一个强类型的类,其中包含每个参数的访问方法。这些类有助于确保参数是正确类型的,并防止因不正确的参数值而引起的运行时错误。这使得这种传递参数的方式是类型安全的,您可以在使用 Android 导航组件 时使用它。

因此,您可以像这样定义您的片段:

<fragment
    android:id="@+id/destination_fragment"
    android:name="packageName.DestinationFragment">

    <argument
        android:name="firstArg"
        app:argType="integer"
        android:defaultValue="0" />

    <argument
        android:name="secondArg"
        app:argType="string"
        android:defaultValue="" />

</fragment>

然后,使用 Safe Args 传递参数启动此片段:

val action = FragmentDirections.actionSourceFragmentToDestinationFragment(firstArg = 12345, secondArg = "Hello World!")
findNavController().navigate(action)

更新

当您使用标准的方式在片段之间传递对象时,它在编译时不会被检查。因此,例如,如果您将 Int 值放入 Bundle 中,并尝试使用相同的键获取 String,它将返回默认值

例如,在下面的示例中,value 变量的值将为 null

    val bundle = Bundle().apply {
        putInt("key", 1)
    }
    
    val value = bundle.getString("key")
    
    println(value) // null!

您可以看到为什么会发生这种情况,这是因为 BaseBundle.getString() 方法:

@Nullable
public String getString(@Nullable String key) { // key = "hey"
    unparcel();
    final Object o = mMap.get(key); // 这里我们得到了 Integer = 1
    try {
        return (String) o; // 尝试将 Integer 强制转换为 String
    } catch (ClassCastException e) {
        typeWarning(key, o, "String", e);
        return null; // 捕获到 ClassCastException => 返回 null!
    }
}
英文:

Yes, you provided the regular form of passing arguments between fragments. This is type safe because Bundle class provide API to put and get data of different types. It means that you will not encounter ClassCastException using it (see detailed explanation below)

> So what does it mean when we say that safe args are type safe?

I presume you're talking about Safe args, which is a Gradle plugin for Android that provides a type-safe and easy-to-use mechanism for passing data between destinations in the Android Navigation component.

With Safe Args, you define the arguments for each destination in an XML file, and the plugin generates a strongly-typed class for each destination that contains accessor methods for each argument. These classes help to ensure that the arguments are of the correct type and prevent runtime errors caused by incorrect argument values. That makes this way of passing type safe and you can use it when you're using Android Navigation component.

So you can define your fragments like this:

<fragment
    android:id="@+id/destination_fragment"
    android:name="packageName.DestinationFragment">

    <argument
        android:name="firstArg"
        app:argType="integer"
        android:defaultValue="0" />

    <argument
        android:name="secondArg"
        app:argType="string"
        android:defaultValue="" />

</fragment>

And start this fragment, passing arguments with Safe Args:

val action = FragmentDirections.actionSourceFragmentToDestinationFragment(firstArg = 12345, secondArg = "Hello World!")
findNavController().navigate(action)

Update

When you use the standard way of passing objects between fragments, it is not being checked on the compile time. So for instance, if you put Int value in a Bundle and try to get a String with a same key it will return the default value.

For example the value of value variable will be null in the example below:

    val bundle = Bundle().apply {
        putInt("key", 1)
    }
    
    val value = bundle.getString("key")
    
    println(value) // null!

You can see why it happens in a BaseBundle.getString() method:

@Nullable
public String getString(@Nullable String key) { // key = "hey"
    unparcel();
    final Object o = mMap.get(key); // Here we got Integer = 1
    try {
        return (String) o; // Trying to cast Integer to String
    } catch (ClassCastException e) {
        typeWarning(key, o, "String", e);
        return null; // ClassCastException was caught => return null!
    }
}

答案2

得分: 0

Regarding to type safe Bundle, I recently build a tool to achieve that.

Leverage on some Kotlin feature (value class, extension function, etc), we can easily use Bundle in a much safer way.

Example usage:

val textKey = StringKey("SomeStringKey")
val intKey = IntKey("SomeIntKey")
val bundle = Bundle()

bundle.put(textKey, "Some text")   // only String value is acceptable
bundle.put(intKey, 12345)          // only Int value is acceptable

val text = bundle.get(textKey, "") // guarantee result to be a String  
val int = bundle.get(intKey, -1)   // guarantee result to be an Int

The concept is we associate type info in the key string like StringKey, IntKey where it's a value class in Kotlin will compile back to String in the end but able to provide type information at build time.
And the extension function get and set will check these info to ensure everything is matched.

GitHub link: https://github.com/Jintin/TypedBundle

英文:

Regarding to type safe Bundle, I recently build a tool to achieve that.

Leverage on some Kotlin feature (value class, extension function, etc), we can easily use Bundle in a much safer way.

Example usage:

val textKey = StringKey("SomeStringKey")
val intKey = IntKey("SomeIntKey")
val bundle = Bundle()

bundle.put(textKey, "Some text")   // only String value is acceptable
bundle.put(intKey, 12345)          // only Int value is acceptable

val text = bundle.get(textKey, "") // guarantee result to be a String  
val int = bundle.get(intKey, -1)   // guarantee result to be an Int

The concept is we associate type info in the key string like StringKey, IntKey where it's a value class in Kotlin will compile back to String in the end but able to provide type information at build time.
And the extension function get and set will check these info to ensure everything is matched.

github link : https://github.com/Jintin/TypedBundle

huangapple
  • 本文由 发表于 2023年5月6日 19:05:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76188520.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定