使用Kotlin委托来简化获取Intent参数的方式

前言

Activity/Fragment获取传递过来的参数的时候,需要使用intent/arguments来获取对应的值,每次都要写一堆的intent.getStringExtra等等的代码,为什么不使用kotlin的委托属性来减少重复代码呢?

默认获取参数方式

下面有几种常用的获取参数的方法

使用默认值

1
2
3
4
5
6
7
8
// 声明
private var test: String? = ""

// Activity获取
test = intent.getStringExtra("test")

// Fragment获取
test = arguments?.getString("test")

注意不能直接在声明属性的时候获取值,如private val test: String = intent.getStringExtra("test")这样写会直接报错的,因为在init的时候getIntent返回的是空。

说下缺点,很明显,每次都要写get的代码,很烦,而且每次使用还要加?

使用lateinit

1
2
3
4
5
6
7
8
// 由于lateinit声明,所以类型不能为空
private lateinit var test: String

// Activity获取
test = intent.getStringExtra("test")

// Fragment获取,因为getArguments返回的值是可空的,所以还要加个判断
test = arguments?.getString("test") ?: ""

说下缺点,同第一种,也是每次都要写,但是比第一种要少加个?

使用lazy(属于kotlin的标准委托)

1
2
3
private val test by lazy {
intent.getStringExtra("test")
}

比上面两种好多了,但是lazy一般会带有额外的开销,除非使用LazyThreadSafetyMode.NONE模式,而且lazy是用val声明,如果要修改,那就得重新声明一个属性…

改变方式

那么有没有好的方法来减少重复代码呢,并且即可声明为val,也可声明为var呢?我们要达到的效果应该是下面这样

1
2
3
private val test by extraAct("test", "")

private var test1 by extraAct("test1", "")

这里就要用到kotlin的委托属性了,有一种属性,在使用的时候每次都要手动实现它,但是可以做到只实现一次,并且放到库中,一直使用,这种属性称为委托属性。具体介绍可见https://www.kotlincn.net/docs/reference/delegated-properties.html

委托属性的级别语法是

val/var <属性名>: <类型> by <表达式>

实现接口,重写方法

ReadWriteProperty

实际上我们要写的就是实现ReadWriteProperty接口,然后在getValuesetValue,进行获取和设置对应的值。

1
2
3
4
5
6
7

interface ReadWriteProperty<in R, T> {

operator fun getValue(thisRef: R, property: KProperty<*>): T

operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

  1. 首先声明一个类,然后实现ReadWriteProperty接口
  2. 创建两个参数,一个是参数名extraName,类型为String;一个是默认值default,泛型
  3. 然后声明一个属性,用于保存获取对应字段的值,可空类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ActivityExtras<T>(private val extraName: String, private val defaultValue: T) :
ReadWriteProperty<Activity, T> {

/**
* getExtras字段对应的值
*/
private var extra: T? = null

override fun getValue(thisRef: Activity, property: KProperty<*>): T {
// 返回值
}

override fun setValue(thisRef: Activity, property: KProperty<*>, value: T) {
// 设置值
}
}

因为我们是在Activity里面获取对应的Intent参数的值,所以thisRef的类型就必须写成Activity

getValue

然后来获取对应的字段的值:

1
2
3
4
override fun getValue(thisRef: Activity, property: KProperty<*>): T {
return extra ?: thisRef.intent?.get<T>(extraName)?.also { extra = it }
?: defaultValue.also { extra = it }
}

来说明下这段代码:

  1. 首先是如果extra不为空,则返回extra
  2. 如果extra是空的,则判断intent的参数的值,如果值不为空,则返回,并且将值赋予extra
  3. 如果intent参数的值也为空,则返回defaultValue,并且将值赋予extra

Intentget扩展见 https://github.com/Ifxcyr/ActivityMessenger

setValue

设置值的话就很简单了,直接将value赋值给extra就行了

1
2
3
override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
extra = value
}

结束

粗劣调用

调用的时候呢,就这样写

1
private val test by ActivityExtras<String?>("test", null)

改进

emmmm,还是有一点区别,如果我不想用默认值的时候,直接替我声明为null。那么就要定义函数,返回的类型是ActivityExtras就可以。

1
2
3
4
fun <T> extraAct(extraName: String): ActivityExtras<T?> = ActivityExtras(extraName, null)

fun <T> extraAct(extraName: String, defaultValue: T): ActivityExtras<T> =
ActivityExtras(extraName, defaultValue)

最终调用效果

1
2
3
4
5
6
7
8
private val test: String? by extraAct("test") // 类型为String?

private val test1 by extraAct<String>("test1") // 类型为String?

private var test2 by extraAct("test2", "1") // 类型为String

// 修改的时候,直接赋值就行
test2 = "123"

结论

委托属性其实很简单,实现了ReadWriteProperty接口之后,在调用属性的getset的时候自动帮你调用对应的getValuesetValue函数,省去了重复的代码操作。
如果是ReadOnlyProperty的话,那么就只提供getValue函数,只能获取,不能修改属性。

对应Fragment代码

对应的Fragment代码,使用的时候,同ActivityExtras

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 获取Intent参数,Fragment
* 示例同[ActivityExtras]
*/
class FragmentExtras<T>(private val extraName: String, private val defaultValue: T) :
ReadWriteProperty<Fragment, T> {

/**
* getExtras字段对应的值
*/
private var extra: T? = null

override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
// 如果extra不为空则返回extra
// 如果extra是空的,则判断arguments的参数的值,如果值不为空,则将值赋予extra,并且返回
// 如果arguments参数的值也为空,则返回defaultValue,并且将值赋予extra
return extra ?: thisRef.arguments?.get<T>(extraName)?.also { extra = it }
?: defaultValue.also { extra = it }
}

override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
extra = value
}
}

fun <T> extraFrag(extraName: String): FragmentExtras<T?> = FragmentExtras(extraName, null)

fun <T> extraFrag(extraName: String, defaultValue: T): FragmentExtras<T> =
FragmentExtras(extraName, defaultValue)

源码地址 https://github.com/Ifxcyr/ActivityMessenger