手记

怎么使用kotlin代理开发Android

使用一些kotlin特性可以很容易写一些简洁和可读性的代码.辛亏有很多data class, 属性, 扩展函数和代理一个kotlin类,和使用Java类相比,他们通常更小和更容易阅读. 在这篇博客里面,我们将可以看到如何去使用Kotlin代理属性去简化Android代码. 如果你不熟悉这个主题,你可以去看看官方文档,或者这篇文章.

延迟代理(Lazy delegate)

kotlin标准库中包含了许多有用的代理,比如创建使用lazy函数来创建一个属性,仅当它第一次被访问时进行初始化. 这个代理很容易初始化一个一个由dagger 管理的一个属性.

 private val navigationController by lazy {
    (applicationContext as GithubApp).conponent.navigationController()
 }

在标准的使用dagger方式中,你不必用上面的这种方式,因为属性将会被初始化是通过componet调用inject方法. 我写了一个demo 工程,你可以在Gtihub上找到我以一种简单的方式使用dagger, Activites和Fragments依赖是手动从dagger中的component中获取的.

之前的代码例子能够通过定义2个扩展属性来简化从Context 或者Fragmet 获取dagger component

val Context.component: AppComponent
   get() = (applicationContext as GithubApp).component

val Fragment.component: AppComponent
    get() = activity.component

现在属性能够被以一种简单的方式来定义:

private val navigationController by lazy {
  compone.navigationController()
}

如果你的类只包含了几个被注入的字段,你可以使用lazy代理来定义他们,你不需要通过调用inject 来定义它们, 这个属性也不需要任何注解(所以没有用到注解处理器), 并且它可以被声明为私有且没有lateinit关键字

映射代理(Map delegate)

在标准库中定义的另外一种代理是Map代理, 可以以一种静态的方式来使用键值对.通常我们需要用一个已经定义好的包含keys的set集合的map,例如使用Firebase云消息服务器返回的参数map:

class MyMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(message: RemoteMessage?) {
     super.onMessageReceived(message)
     val data = (message?.data ?: emptyMap()).withDefualt {""}
     val title = data["title"]
     val content = data["content"]
     print("$title $content")
    }
}

使用已经定义好的string很容易产生错误, 我知道你可以在某处定义一个常量,但是定义一个静态常量是Java1.4的风格(或者说在kotlin 定义一个object).我们能够改进我们的代码通过定义一个带有2个属性的类,其中属性是由使用map代理来管理的.

class NotificationParams(val map: Map<String, String>) {
    val title: String by map
    val content: String by map
}

代码可以使用上面的类来重写, 如果字段(对应map中的key)有错误,它将不会编译成功.

    override fun onMessageReceived(message: RemoteMessage?) {
        super.onMessageReceived(message)
        val data = (message?.data ?: emptyMap()).withDefault{ "" }
        val params = NotificationParams(data)
        print("${params.title} ${params.content}")
    }

SharedPreferences
在github上有很多kotlin库用来简化sharedpreferences的操作,让我们来写一个自定义delegate去写去保存一个属性在shared preference. 首先让我们来写一个sharedpreference的扩展函数,这个函数定义如下:

fun SharedPreferences.Int(defaultValue: Int = 0, key: String? = null): ReadWriteProperty<Any, Int> {
    override fun getValue(thisRef: Any, property: KProperty<*>) = 
        getInt(key ?: propert.name, defaultValue)

    override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) =  
    edit().putInt(key ?: property.name, value).apply()
}

如果你对kotlin不熟悉的话,上面的代码不是很容易理解(也有可能你对它很熟悉).定义了一个SharedPreference 对象上一个int扩展方法. 这个方法返回一个定义了read 和 write的ReadWriteProperty. 它由2个可选参数, defaultValue和用来存储值的sharedpreference的key(如果key没有提供,这个属性名称将会被使用)

使用这个方法我们可以定义一个带有字段的类来连接sharedpreference:

class MyClass(prefs: SharedPreferences) {
    var count by prefs.int()
}

这个属性关联着来自shared preferences value的读或者写

我们可以为其他的类型定义类似的方法, 为了避免拷贝和粘贴,我们可以使用泛型方法(我们知道, 这会减小可读性)

    private inline fun <T> SharedPreferences.delegate(defualtValue: T, key: String?, crossinline getter: SharedPreferences.(String, T) -> T, 
crossinline setter: Editor.(String, T) -> Editor): ReadWriteProperty<Any, T> {
    return object: ReadWriteProperty<Any, T> {
         override fun getValue(thisRef: Any, property: KProperty<*>) =
        getter(key ?: property.name, defaultValue)

         override fun setValue(thisRef: Any, property: KProperty<*>, 
            value: T) =
        edit().setter(key ?: property.name, value).apply()
    }
}

这里有2个额外的参数(当delegate函数被调用时, 这2个函数是用来简化操作的)

  • getter 函数时用来从SharedPreferences 对象中读取值

  • setter 函数通过Editor来写值

这个函数被定义成inline是为了避免运行时开销, 使用inline这个关键字, getter 和 setter参数不会被转换成一个以字节码的形式的类(更多信息可以查找官方文档)

那么我们很容易用shared preferences 为所有的类型写一些方法(事实上kotlin泛型用在基本类型是非常有用的)

 fun SharedPreferences.int(def: Int = 0, key: String? = null) =
   delegate(def, key, SharedPreferences::getInt, Editor::putInt)

fun SharedPreferences.long(def: Long = 0, key: String? = null) =
   delegate(def, key, SharedPreferences::getLong, Editor::putLong)

这些代理能够被用在存储token和 count 的类上, 这个count 是用来统计token被保存多少次(这只是一个例子,不是特别有用):

    class TokenHolder(prefs: SharedPreferences) {
    var token by prefs.string()
        private set

    var count by prefs.int()
        private set

    fun saveToken(newToken: String) {
        token = newToken
        count++
    }
}

count++被调用时,是首先从sharedpreference读取一个值并且保存在那个值的基础上增加1的值, 像下面这样(但是是可读的和紧凑的形式!)

    prefs.edit().putInt("count", prefs.getInt("count", 0)+1).apply()

SharedPreferences是 android sdk 里面的一个类, 我们可以使用这个代理在很多类里面, 有时我们可以在我们的业务逻辑里面使用这个类, 许多开发者喜欢脱离android 去使用jvm来测试他们. 尽管我梦使用一个android sdk 类, 但是我们可以在jvm上测试这个代理类. 我们能够写一个JVM测试通过使用一个SharedPreferences 假的实现,这个假的实现是使用map 来存储值的.

    @Test fun shouldCount() {
    val prefs = FakeSharedPreferences()
    val tokenHolder = TokenHolder(prefs)

    tokenHolder.saveToken("a")
    tokenHolder.saveToken("b")

    assertThat(tokenHolder.count).isEqualTo(2)
    assertThat(prefs.getInt("count", 0)).isEqualTo(2)
}

你也可以找到一个一个Kotlin版本的FakeSharedPreferences ,原始的Java 版本在这里

这就是第一部分的所有, 在(第二部分)[https://medium.com/@fabioCollini/kotlin-delegates-in-android-development-part-2-2c15c11ff438]中, 我们将会看到如何使用kotlin代理去简化架构组件的使用

原文链接:http://www.apkbus.com/blog-914653-68406.html

0人推荐
随时随地看视频
慕课网APP