26.2 Accessing Shared Preferences
You probably know what Android
Shared Preferences²⁸
are. They consist of a set of keys and
values that can be easily saved and restored by using the Android framework. These preferences
²⁸
http://developer.android.com/training/basics/data-storage/shared-preferences.html
26 Settings Screen
128
are integrated with some parts of the SDK to make some tasks easier. Besides, since Android 6.0
(Marshmallow), shared preferences can be automatically cloud-saved, so when a user restores the
App in a new device, these preferences are restored too.
Thanks to the use of property delegation, we can deal with preferences in a very easy way. We can
create a delegate that queries the preference when
get
is called and saves it when we call to
set
.
As we want to save the zip code, which is a long value, let’s create a delegate for
Long
properties. In
DelegatesExtensions.kt
, implement a new
LongPreference
class:
1
class LongPreference(val context: Context, val name: String, val default: Long)
2
:
ReadWriteProperty {
3
4
val prefs by lazy {
5
context.getSharedPreferences("default", Context.MODE_PRIVATE)
6
}
7
8
override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
9
return prefs.getLong(name, default)
10
}
11
12
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Long) {
13
prefs.edit().putLong(name, value).apply()
14
}
15
}
First, we create a lazy access to preferences. That way, if we don’t use the property, this delegate
will never request the
SharedPreferences
object.
When
get
is called, its implementation will use the preferences instance to retrieve a long property
with the name that was specified in the delegate declaration, and defaulting to the
default
value if
the property is not found. When a value is set, a preferences editor is requested and the value will
be saved using the name of the property.
We can then define the new delegate in the
DelegatesExt
object, so that it’s easier to access when
required:
1
object DelegatesExt {
2
....
3
fun longPreference(context: Context, name: String, default: Long) =
4
LongPreference(context, name, default)
5
}
In
SettingsActivity
, a property to deal with zip code preference can now be defined. I’m also
creating a couple of constants which keep the name and default value of the property. That way
they can be used in other sections of the App.
26 Settings Screen
129
1
companion object {
2
val ZIP_CODE = "zipCode"
3
val DEFAULT_ZIP = 94043L
4
}
5
6
var zipCode: Long by DelegatesExt.longPreference(this, ZIP_CODE, DEFAULT_ZIP)
Now it’s really easy to work with this preference. In
onCreate
, we get the value of the property and
assign it to the
EditText
:
1
override fun onCreate(savedInstanceState: Bundle?) {
2
...
3
cityCode.setText(zipCode.toString())
4
}
We can’t use the auto-generated property
text
because
EditText
returns an
Editable
in
getText
,
so the property is defaulted to that value. If we try to assign a
String
, the compiler will complain.
Using
setText()
will be enough.
Now we have everything we need to implement
onBackPressed
. Here, the new value of the property
is saved:
1
override fun onBackPressed() {
2
super.onBackPressed()
3
zipCode = cityCode.text.toString().toLong()
4
}
The
MainActivity
requires some little changes. First, it also needs a
zipCode
property.
1
val zipCode: Long by DelegatesExt.longPreference(this, SettingsActivity.ZIP_CODE,
2
SettingsActivity.DEFAULT_ZIP)
And then, I’m moving the forecast load to
onResume
so that every time the activity is resumed, it
refreshes the data, just in case the zip code changed. Of course, there are more efficient ways to
do this, by checking whether the zip code really changed before requesting the forecast again, for
instance. But I want to keep this example simple, and the requested info is already saved in a local
database, so this solution is not that bad:
26 Settings Screen
130
1
override fun onResume() {
2
super.onResume()
3
loadForecast()
4
}
5
6
private fun loadForecast() = async {
7
val result = RequestForecastCommand(zipCode).execute()
8
uiThread {
9
val adapter = ForecastListAdapter(result) {
10
startActivity (DetailActivity.ID to it.id,
11
DetailActivity.CITY_NAME to result.city)
12
}
13
forecastList.adapter = adapter
14
toolbarTitle = "${result.city} (${result.country})"
15
}
16
}
The
RequestForecastCommand
is now using the
zipCode
property instead of the previous fixed value.
There’s just one more thing we must do: start the settings activity when the user clicks on the
overflow action. In
ToolbarManager
, the
initToolbar
function requires a small change:
1
when (it.itemId) {
2
R.id.action_settings -> toolbar.ctx.startActivity()
3
else -> App.instance.toast("Unknown option")
4
}
Do'stlaringiz bilan baham: |