访问Shared Preferences

你可能知道什么是Android Shared Preferences。可以通过Android框架简单存储的一系列key和value对。这些preferences与SDK的一部分融为一体,使得任务变得更加容易。而且从Android 6.0(Marshmallow),shared preferences可以自动被云存储,所以当一个用户在一个新的设备上面恢复App的时候,它们的preferences也会被恢复。

多亏使用了属性委托,我们可以使用非常简单的方式来处理preferences。我们可以创建一个委托,当get被调用时去查询,当set被调用时去执行保存操作。

因为我们想去保存zip code,它是一个long型,所以让我们创建一个Long属性的委托吧。在DelegatesExtensions.kt中,实现一个新的LongPreference类:

class LongPreference(val context: Context, val name: String, val default: Long)
    :  ReadWriteProperty<Any?, Long> {

    val prefs by lazy {
        context.getSharedPreferences("default", Context.MODE_PRIVATE)
    }

    override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
        return prefs.getLong(name, default)
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Long) {
        prefs.edit().putLong(name, value).apply()
    }
}

首先,我们使用lazy委托的方式创建一个preferences。这样的话,如果我们没有使用这个属性,这个委托就不会请求这个SharedPreferences对象。

get被调用,它的实现是使用preferences实例去获取一个委托声明中指定名字的long属性,如果没有找到这个属性,则默认使用default。当一个值被set,拿到preferences editor并使用属性名保存。

我们可以在DelegatesExt中定义一个新的委托,这样我们访问时就简单很多:

object DelegatesExt {
    ....
    fun longPreference(context: Context, name: String, default: Long) =
        LongPreference(context, name, default)
}

SettingActivity,现在可以定义一个属性去处理zip code偏好。我创建了两个常量用来作为名字和属性的默认值。这种方式可以在App其他地方使用:

companion object {
    val ZIP_CODE = "zipCode"
    val DEFAULT_ZIP = 94043L
}

var zipCode: Long by DelegatesExt.longPreference(this, ZIP_CODE, DEFAULT_ZIP)

现在preference工作起来就非常简单了,我们可以从属性中得到并赋值给EditText

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    cityCode.setText(zipCode.toString())
}

我们不能使用自动生成的属性text,因为EditTextgetText中返回的是Editable,所以该属性默认为该值。如果我尝试去分配一个String,编译器会报错,使用setText()就足够了。

现在具备了所有要实现onBackPressed的东西。这里,一个属性的新值会被储存:

override fun onBackPressed() {
    super.onBackPressed()
    zipCode = cityCode.text.toString().toLong()
}

MainActivity需要一些小的改变。首先,它也需要一个zip code属性。

val zipCode: Long by DelegatesExt.longPreference(this, SettingsActivity.ZIP_CODE,
            SettingsActivity.DEFAULT_ZIP)

然后,我把forecast load的代码移动到了onResume,这样每次activity resumed,它都会刷新数据,以防code zip被修改。当然这里有更加复杂一点的方式去做,比如通过在请求forecast之前检查是否zip code真的改变了。但是我像保持这个例子的简单性,而且因为请求的数据已经保存在本地数据库中了,所以这个解决方案也不算太坏:

override fun onResume() {
    super.onResume()
    loadForecast()
}

private fun loadForecast() = async {
    val result = RequestForecastCommand(zipCode).execute()
    uiThread {
        val adapter = ForecastListAdapter(result) {
            startActivity<DetailActivity>(DetailActivity.ID to it.id,
                    DetailActivity.CITY_NAME to result.city)
        }
        forecastList.adapter = adapter
        toolbarTitle = "${result.city} (${result.country})"
    } 
}

RequestForecastCommand现在使用zipCode而不是之前的是一个固定值。

这里还有意见我们必须要做的事情:当溢出菜单的settings点击时启动这个setting activity。在ToolbarManager中的initToolbar函数需要有一些小的修改:

when (it.itemId) {
    R.id.action_settings -> toolbar.ctx.startActivity<SettingsActivity>()
    else -> App.instance.toast("Unknown option")
}