访问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类:

  1. class LongPreference(val context: Context, val name: String, val default: Long)
  2. : ReadWriteProperty<Any?, Long> {
  3. val prefs by lazy {
  4. context.getSharedPreferences("default", Context.MODE_PRIVATE)
  5. }
  6. override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
  7. return prefs.getLong(name, default)
  8. }
  9. override fun setValue(thisRef: Any?, property: KProperty<*>, value: Long) {
  10. prefs.edit().putLong(name, value).apply()
  11. }
  12. }

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

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

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

  1. object DelegatesExt {
  2. ....
  3. fun longPreference(context: Context, name: String, default: Long) =
  4. LongPreference(context, name, default)
  5. }

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

  1. companion object {
  2. val ZIP_CODE = "zipCode"
  3. val DEFAULT_ZIP = 94043L
  4. }
  5. var zipCode: Long by DelegatesExt.longPreference(this, ZIP_CODE, DEFAULT_ZIP)

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

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

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

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

  1. override fun onBackPressed() {
  2. super.onBackPressed()
  3. zipCode = cityCode.text.toString().toLong()
  4. }

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

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

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

  1. override fun onResume() {
  2. super.onResume()
  3. loadForecast()
  4. }
  5. private fun loadForecast() = async {
  6. val result = RequestForecastCommand(zipCode).execute()
  7. uiThread {
  8. val adapter = ForecastListAdapter(result) {
  9. startActivity<DetailActivity>(DetailActivity.ID to it.id,
  10. DetailActivity.CITY_NAME to result.city)
  11. }
  12. forecastList.adapter = adapter
  13. toolbarTitle = "${result.city} (${result.country})"
  14. }
  15. }

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

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

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