RawPreferenceField
The first logical step for Android™ support is to integrate with shared preferences because they change somewhat frequently, listening for changes is rarely done and not so straightforward, but can help make the app feel more reactive.
The most low-level way to bind a field to a preference is using a RawPreferenceField
. The value is always nullable because the preference could be not present. It offers no option for a default value because it needs to mirror exactly the internal value of the preferences.
The functions to create it are extensions of the SharedPreferences
class. As usual, for java they can be called statically in a special utility class. A complete list can be found here.
val sp = context.getSharedPreferences("test_prefs") //Obtain SharedPreferences instance
val f /* : RawPreferenceField<String?> */ = sp.getRawStringField("pref_key")
f.value = "hello" //Sets the preference to "hello"
f.value = null //Removes the preference
SharedPreferences sp = getContext().getSharedPreferences("test_prefs"); //Obtain SharedPreferences instance
RawPreferenceField<String> f = SharedPreferencesUtils.getRawStringField(sp, "pref_key");
f.setValue("hello"); //Sets the preference to "hello"
f.setValue(null); //Removes the preference
The returned field is a special implementation of MutableField
that will allow you to read and change the value of the shared preference. Setting it to null
will remove the preference. It also extends the BasePreferenceField
class that exposes these three properties:
key
: theString
containing the preference key associated with the fieldpreferences
: theSharedPreferences
object associated with the keydefaultValue
: the default value (in this case this value will always benull
)
Internally a listener is registered to the shared preferences so we can have multiple instances pointing at the same key without any problem. When the value is updated it is immediately applied.
Notice that the value mirrors exactly what is stored in the preferences: in this way we do not incur in problems related to the fields' laziness (i.e. the value doesn't change if it's the same).
Preference fields can be created for all types supported by Android™'s shared preferences (Boolean
, Int
, Long
, Float
, String
, Set<String>
), but what if we want to store something a little more complex? Most of the times we store in the shared preferences a value that represents something but is not the whole object (like an ID). In this cases we can simply apply a two-way transformation, either explicitly by calling twoWayTransform() on the returned field or by passing the two functions directly to the get field function, like so:
val f /* : RawPreferenceField<Movie?> */ = sp.getRawLongField("favorite_movie", { Movie.byIdOrNull(it) }, { it.id })
f.value = avatar //Sets Avatar's ID to the preference
f.value = null //Removes the preferece
SharedPreferences sp = getContext().getSharedPreferences("test_prefs"); //Obtain SharedPreferences instance
RawPreferenceField<Movie> f = SharedPreferencesUtils.getRawLongField(sp, "favorite_movie", id -> Movie.byIdOrNull(id), movie -> movie.id);
f.setValue(avatar); //Sets Avatar's ID to the preference
f.setValue(null); //Removes the preference
The first function maps an low-level value to a high-level one, the second one is the opposite. In the example the first retrieves a movie from a saved Long
ID, while the second one maps a movie to its ID that will be saved in the preferences.
Since null
is used to represent the absence of the preference we cannot map it to a low-level value, however we are still able to return null
in the low to high level one because we want the possibility to handle incorrect values. In the example above a movie with the saved ID could not be found for a lot of reasons. When this happens, the function yields null
which then "pongs" back and removes the preference.