高阶函数的应用:简化 SharedPreferences 与 ContentValues 操作

前言

在 Kotlin 中,我们可以利用高阶函数和扩展函数,来简化各种 API 繁琐的样板代码。

现在,我们就来简化 SharedPreferencesContentValues 这两个常用 API 的用法。

简化 SharedPreferences 的用法

我们先来回顾一下,之前我们向 SharedPreferences 中存储数据的代码:

kotlin 复制代码
val preferences = getSharedPreferences("data", Context.MODE_PRIVATE)
val editor = preferences.edit()
editor.putString("name", "Martin")
editor.putInt("age", 23)
editor.putString("gender","male")
editor.apply()

这种写法使用了 Java 的编程思维。在 Kotlin 中,我们可以做得更好。

我们可以定义一个扩展函数,利用高阶函数来封装 edit()apply() 的过程。

创建 SharedPreferencesExtensions.kt 文件,代码如下:

kotlin 复制代码
fun SharedPreferences.transaction(block: SharedPreferences.Editor.() -> Unit) {
    val editor = edit()
    editor.block()
    editor.apply()
}

我们来详细讲解一下:

  • transaction 是一个 SharedPreferences 类的扩展函数 ,所以在函数中,我们拥有 SharedPreferences 的上下文,可以直接调用 SharedPreferences 实例的 edit() 方法。

  • 并且这个函数接收一个函数类型的 block 参数,所以它也是一个高阶函数block 参数带有 SharedPreferences.Editor 接收者,这意味着我们传入的 Lambda 表达式中,拥有 SharedPreferences.Editor 的上下文,可以直接调用 SharedPreferences.Editor 实例的 put() 系列方法来添加数据,例如 putString()

函数内部还完成了 edit()apply() 的调用,使得我们无需手动创建 Editor 对象和手动提交数据。

现在,向 SharedPreferences 中存储数据的代码将会简化为:

kotlin 复制代码
getSharedPreferences("data", Context.MODE_PRIVATE).transaction {
    putString("name", "Martin")
    putInt("age", 23)
    putString("gender", "male")
}

代码更少了,而且逻辑很清晰。

KTX 扩展库简化方式

其实 KTX 扩展库androidx.core:core-ktx)已经提供了功能相同的简化方式。我们可以直接通过以下写法来向 SharedPreferences 中存储数据:

kotlin 复制代码
getSharedPreferences("data", Context.MODE_PRIVATE).edit {
    putString("name", "Tom")
    putInt("age", 28)
    putBoolean("married", false)
}

我们来看看它的源码:

kotlin 复制代码
@SuppressLint("ApplySharedPref")
public inline fun SharedPreferences.edit(
    commit: Boolean = false,
    action: SharedPreferences.Editor.() -> Unit
) {
    val editor = edit()
    action(editor)
    if (commit) {
        editor.commit()
    } else {
        editor.apply()
    }
}

可以发现它将函数定义为了内联函数,提高了性能,并且还可以让我们选择此次提交方式(applycommit)。一般来说,优先使用官方函数。

简化 ContentValues 的用法

同样,之前构建一个 ContentValues 对象的代码可能是下面这样的:

kotlin 复制代码
val values = ContentValues()
values.put("name", "Game of Thrones")
values.put("author", "George Martin")

当然,聪明的你,可能会使用 apply 函数来稍微简化一下构建过程,变为这样:

kotlin 复制代码
val valuesWithApply = ContentValues().apply {
    put("name", "Game of Thrones")
    put("author", "George Martin")
}

虽然这种写法已经很好了,但这也不是最优的。它是命令式的风格,不是声明式的风格,我们现在来实现直接接收多个键值对来创建 ContentValues 对象。

首先,我们新建一个 ContentValuesExtensions.kt 文件,在其中定义一个 cvOf() 方法。代码如下:

kotlin 复制代码
fun cvOf(vararg pairs: Pair<String, Any?>): ContentValues {
    // TODO 实现方法的逻辑
    return ContentValues()
}

我们来稍微解释一下:

  • 该方法用于构建一个 ContentValues 对象。

  • pairs 参数的类型是 Pair,Pair 在 Kotlin 中表示键值对的数据结构。

    因为 ContentValues 的键(key)都是字符串类型的,所以我们将 Pair 的第一个泛型参数定为了 StringContentValues 的值(value)可以有多种类型,所以将第二个泛型参数指定成 Any?Any 是 Kotlin 中所有类型的基类,相当于 Java 中的 Object,这表示可以传入任意类型,并且在 Any 后加上了 ?,说明传入的值可为空。

  • 我们还在 pairs 参数前加上了 vararg 关键字,这表示该参数是可变参数,我们可以传入任意数量的 Pair 对象,编译器最终会将它们装进一个数组中。

接下来,我们实现方法的逻辑。

核心思路很简单:先创建一个 ContentValues 对象,然后遍历 pairs 参数,取出每一个 Pair 对象,将其键和值添加到 ContentValues 对象中,最后返回该 ContentValues 对象即可。

但有一个问题:每个 Pair 对象值的类型都是 Any?,我们并不知道它的具体类型是什么,所以我们无法确定该调用哪个 put() 重载方法往 ContentValues 对象中添加数据。

其实 when 语句就可以进行类型匹配,我们可以利用它来判断值的类型。

实现后的代码如下所示:

kotlin 复制代码
fun cvOf(vararg pairs: Pair<String, Any?>): ContentValues {
    // 创建 ContentValues 对象
    val cv = ContentValues()

    // 遍历 Pair 对象
    for (pair in pairs) {
        // 获取键
        val key = pair.first
        // 获取值
        when (val value = pair.second) {
            // 根据值的类型,来判断该调用哪个 put 方法
            is Int -> cv.put(key, value)
            is Long -> cv.put(key, value)
            is Short -> cv.put(key, value)
            is Float -> cv.put(key, value)
            is Double -> cv.put(key, value)
            is Boolean -> cv.put(key, value)
            is String -> cv.put(key, value)
            is Byte -> cv.put(key, value)
            is ByteArray -> cv.put(key, value)
            null -> cv.putNull(key)
            else -> throw IllegalArgumentException("Unsupported value type: ${value.javaClass.name} for key '$key'")
        }
    }
    return cv
}

其中关键在于:

  • 我们使用了 when(value) 语句来判断 value 的具体类型,从而能够调用具体的 put 方法。

  • 另外,我们用到了 Kotlin 的智能类型转换(Smart Cast)功能。如果进入了某个类型分支内部,编译器会自动 将 value 变量的类型转为该类型,所以我们可以直接调用具体的 put() 方法,无需手动进行类型转换。这个功能在 if 语句中也能使用。

现在,我们构建一个 ContentValues 对象的代码会被简化为:

kotlin 复制代码
val values = cvOf(
    "name" to "Game of Thrones", 
    "author" to "George Martin",
    "pages" to 720, 
    "price" to 20.85
)

注意:使用 A to B 这样的语法结构可以创建一个 Pair(A,B) 对象。

最后,我们可以使用 apply 函数来对 cvOf 函数的实现做进一步简化,简化后:

kotlin 复制代码
fun cvOf(vararg pairs: Pair<String, Any?>) = ContentValues().apply {
    // 遍历 Pair 列表
    for (pair in pairs) {
        // 获取键
        val key = pair.first
        // 获取值
        when (val value = pair.second) {
            // 根据值的类型,来判断该使用哪个方法
            is Int -> put(key, value)
            is Long ->put(key, value)
            is Short -> put(key, value)
            is Float -> put(key, value)
            is Double -> put(key, value)
            is Boolean -> put(key, value)
            is String -> put(key, value)
            is Byte -> put(key, value)
            is ByteArray -> put(key, value)
            null -> putNull(key)
            else -> throw IllegalArgumentException("Unsupported value type: ${value.javaClass.name} for key '$key'")
        }
    }
}

因为 apply 函数的内部拥有 ContentValues 的上下文,所以可以直接调用 put() 方法,无需通过 cv. 来调用。

KTX 扩展库简化方式

SharedPreferences 一样,KTX 库中也提供了一个同样功能的 contentValuesOf() 方法,用法如下所示:

kotlin 复制代码
val values = contentValuesOf(
    "name" to "Game of Thrones",
    "author" to "George Martin",
    "pages" to 720,
    "price" to 20.85
)
相关推荐
哲科软件13 分钟前
跨平台开发的抉择:Flutter vs 原生安卓(Kotlin)的优劣对比与选型建议
android·flutter·kotlin
移动开发者1号15 小时前
Android 同步屏障(SyncBarrier)深度解析与应用实战
android·kotlin
移动开发者1号15 小时前
深入协程调试:协程调试工具与实战
android·kotlin
fundroid1 天前
Kotlin 协程:Channel 与 Flow 深度对比及 Channel 使用指南
android·kotlin·协程
移动开发者1号1 天前
深入理解原子类与CAS无锁编程:原理、实战与优化
android·kotlin
移动开发者1号1 天前
深入理解 ThreadLocal:原理、实战与优化指南
android·kotlin
Devil枫2 天前
Kotlin高级特性深度解析
android·开发语言·kotlin
ChinaDragonDreamer2 天前
Kotlin:2.1.20 的新特性
android·开发语言·kotlin
每次的天空2 天前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
移动开发者1号3 天前
使用 Android App Bundle 极致压缩应用体积
android·kotlin