[MUSIC PLAYING]
LOUIS PULLEN-FREILICH: Hi, everyone.
I'm Louis, and I'm an engineer on the Android Toolkit team.
Today, I'm going to be sharing some best practices when
using the AndroidX Preference library, part of Jetpack.
I'm going to be covering some background behind Preferences
and try to clear up any confusion between preferences
and shared preferences.
I'll demonstrate how to build a settings screen
and some useful features you can use along the way.
And I'm going to cover how you can use multiple settings
screens, which is especially useful if you're
migrating your existing architecture to the AndroidX
library.
But first some background.
What are preferences?
The Preference library is a framework
for building interactive settings screens.
It handles building and updating the UI, so all you need to do
is to find a list of settings to be displayed to the user.
And it also handles interacting with device storage,
so that any value that the user changes will ultimately get
persisted to the device without you having to worry about it.
Preferences itself currently exists in two separate forms.
First, the Framework Preference API.
This is being bundled with the Android
framework since the first version of Android,
so 11 years ago now.
Since it is part of the framework, any new features
and bug fixes we add only make their way
to the newer versions of Android.
And given that the design has changed a lot over the last 11
years as well, the Framework API is themed differently,
depending on which version of Android
the device is running on.
As a result of these, we're no longer
maintaining the Framework API.
And instead, we recommend using the AndroidX library.
The AndroidX Preference library, previously known
as the v7 and v14 Preference Support libraries,
is unbundled from the framework.
This means it can be regularly updated
with any new features and bug fixes we push.
The library itself is backwards compatible down to API 14
and uses the same updated material theme
across all your devices.
This results in a consistent experience for your users.
And how does this all relate to SharedPreferences
you may be wondering?
The shared preference APIs allow you
to persist key-value pairs of data to the device.
They're used internally by the Preference library
to save and retrieve any values that a user configures,
but aren't actually part of the library
itself-- just a lower layer.
So let's start building our settings screen.
We're going to start off by loading the simple hierarchy
on the right.
We have a preference with a switch that allows the user
to toggle state and a preference that just displays text.
I'm going to cover how you should structure
your architecture and also some basic
attributes you should be aware of when you're
using the Preference library.
So we're going to start at the top
with a simple activity that just acts
as a container for a hierarchy.
This should be an AppCompatActivity
with an AppCompat-based theme.
The activity itself is basically just a container
for this PreferenceFragmentCompat, which
is the main entry point when using the preference library.
All our interesting preferences and configuration
will happen in here.
And this fragment itself just wraps a hierarchy
of individual preferences, which can
either be defined in an XML resource
or defined programmatically at runtime.
In this talk, we're going to focus on XML.
So the activity itself is, again, just a simple container
for our fragments.
So we just have some simple fragment boilerplate,
and all we need to do here is just
show our settings fragment.
The fragment, again, fairly simple.
All you need to do is overwrite this one method,
onCreatePreferences, and then just setup the hierarchy
that you're going to inflate and show to the user.
All the interesting definition actually happens in the XML.
So this is our XML file.
We start with a root PreferenceScreen object,
and this basically is the main container
for the entire hierarchy.
Everything should go inside of this,
and this should be the root element.
Note that you should also place your preference.xml resource
files in the res/xml directory.
So now we can add our first preference.
This is a basic preference.
It doesn't have any widget associated with it.
And we have these three important attributes here.
The title and the summary are the visual title
and the visual summary actually displayed
to the user on the screen, as you can see on the right.
And the key is pretty important for two reasons.
Firstly, if this preference were to persist any state
to the device, then this is the key
that the SharedPreference instance would use.
And also this key allows you to interact with this preference
later on in your life cycle.
So it's very important to declare a key
for every preference in your hierarchy.
And now we can add a SwitchPreferenceCompat.
This is a type of preference, very similar to before,
but now we have a switch widget.
So whenever the user toggles to the state of this preference,
this will update the Boolean value persisted
to the key defined here.
Now that we've built the simple hierarchy,
I'll show you some other useful features
we can add to make this even better.
Here's the hierarchy we're going to end up with.
There's quite a lot more going on here,
so we're going to take it step by step.
First, I'm going to cover Categories,
which allow you to visually split up
a complex screen into groups of similar preferences.
Then, preferences that open dialogues,
which allow for more complex configuration than just
a simple Boolean switch.
I'm going to talk a bit about dynamic summaries
and also cover dependencies.
So continuing from where we left off before--
these are the same preferences I had before,
just without the root PreferenceScreen tag,
for verbosity.
One of these controls syncing and one
just displays static information about the application.
Although, this is kind of simple now--
there's only two things in the hierarchy--
the more we add to this hierarchy,
the harder it's going to be for a user to actually see
what preference does what.
We can use preference categories to help separate this up
into logical subsections.
To do this, we simply wrap these preference objects
with a PreferenceCategory tag.
And as you can see, this now adds an accented title
above these groups.
Again, it's important to set a key for these,
so it would be considered a correctly persisted state.
And the title again, same as before, same attribute,
just displayed slightly differently.
Next, we're going to add an EditTextPreference,
similar to a SwitchPreferenceCompat in that
it's a subset of a preference.
This preference uses a dialogue to allow
persisting a string value.
So when the user taps on this preference,
we automatically open a dialogue for you,
which contains an editable text field.
And when the user taps on OK, this value is then persisted.
However, this gives us some interesting questions.
What do we set for the summary here?
Right now, there's no summary, so it's quite hard for the user
to see what the current state is.
And we would like to show the user what they've actually
saved, so they know what they've done.
But since this can change dynamically at runtime,
we can't just put a string here.
The answer here is to use a simple summary provider.
This is added recently in the 1.1 Alpha release.
And it's part of a broad category of things
we call Dynamic Summaries.
I'll cover more about that later on.
But this simple attribute, when set to true,
will mean that the preference summary will automatically
display either the value saved for the preference or the text
not set, if there isn't any value set.
So now we have a way for the user to set a sync code,
and they can see the value they've set automatically.
However, there's one problem here.
It doesn't really make much sense
for the user to be able to change this field if syncing
actually isn't enabled.
We can use a dependency here to fix that.
Essentially, setting a dependency
allows another preference to control
the state of this preference.
So we set the key of the SwitchPreferenceCompat
that will control this preference.
So this means that whenever the switch preference is turned
off, as it is here, the actual preference is grayed out
and can no longer be interacted with.
And when the switch is turned on,
the preference returns to normal and can be interacted with.
Our hierarchy is almost complete from the example that we gave.
The only thing we'd now like to do
is set a useful summary for this switch.
Right now, Enable Periodic Syncing
is on, but what does that actually mean?
Have we synced recently?
Maybe there's a problem connecting to the server.
So we can use a custom dynamic summary here
to display to the user when we've lost sync to providing
some more useful context.
In general, dynamic summaries are
useful in two sort of broad scenarios.
Firstly, if you'd like to provide the user with more
context that is tied to a specific preference,
and what it means.
So in this case, we'd like to tell them when
we've last synced the device.
Or it can also be useful if you'd
like to display some dynamic information.
This could be something like a version information
of your application or even user information,
such as a specific ID or email address.
To set this dynamic summary, we use the SummaryProvider
interface.
We can use this to either react to the internal state
of the preference or just use it as a callback
to generate our own dynamic summary from external state.
Here, we set the type as SwitchPreferenceCompat.
That is, that is the type of preference
that we're setting the summary for.
When the preferences are checked, in other words,
when syncing is enabled, we want to just display that last time
we successfully synced.
And otherwise, we're just going to say
it's disabled, so it's clear to the user.
And this is all there really is to it.
All we need to do now is just set this on the Preference.
So to do that, we call FindPreference
using the key we previously assigned in the XML.
And then we just set the summary provider.
This and any other similar configuration
should be done in onCreatePreferences
in your PreferenceFragmentCompat.
And now, whenever I use the toggles instead
of the preference, the summary will automatically
be updated based on the constraints we defined.
Now that we've built up a more advanced hierarchy,
I'm going to cover using separate hierarchies
in separate screens.
Oftentimes, you'll want to split up more complex hierarchies
into separate screens, making sure
that less important preferences aren't cluttering up
your main screen and making it hard for the user
to focus on the things they actually want to change.
If you're already using the Framework API
and looking to migrate, there are some different ways
you may have structured your separate screens.
And if you're starting out fresh with the AndroidX Preference
library, you may be wondering what the best
way of handling this is.
But first, let's just recap how a single screen looks.
We have our activity at the top, which will internally
wrap a fragment, which then contains its actual hierarchy.
We can kind of consider the fragment
in the hierarchy to be sort of a screen of settings.
So in reality, we just have an activity
which loads a setting screen, which is this.
And this works fine when we have one screen.
But what if our hierarchy looked something like this?
Now on our first screen, we have links to other screens.
So the sync will link to a screen
of sync-related preferences and Messages
will link to some more specific messages-related preferences.
How does this look like with our architecture?
It's actually very simple to what we've done previously
with a single screen.
The Initial Settings screen will just
be just as before-- a PreferenceFragmentCompat
with a hierarchy.
And then the Sync-Scanning screen
will also be a PreferenceFragmentCompat
with its own hierarchy, and so on for any other screens
you may have.
What we need to do now differently
is just the final link between the SyncPreference object
and the SyncSettings fragment, so whenever
the user clicks on this preference,
we will load the related screen.
So we'll start off in this state, pointing
to the Initial Settings screen.
When the user taps a preference, now
that we've already defined this link,
we're just going to swap out the screen from underneath.
And because we're still using Preferences to act as the link,
the Initial Settings hierarchy is
going to be just as we've done before.
So again, we start with the root PreferenceScreen tag,
and we just have two basic preferences--
one for syncing and one for messages.
It's important, while you may want to add an icon here
to the start of the preference, to provide some more context
to the user, as we don't really have an important summary
display here.
This is just done by defining a drawable with the Icon
attribute.
So this will just be something in your res/drawable directory.
And now the only thing left to do
is just to find the links between the fragments.
So to do this, we use the Fragment attribute,
and we just set the fully-qualified class
name of the fragment.
If your fragment is nested in a class,
you'll need to use the dollar sign
to separate the outer class and the inner class.
And that's it for the hierarchy.
The activity is very similar to what we had before.
We just set the Initial Settings screen to be displayed.
The only difference here is that we
should implement this method, onPreference.Fragment.
This method is called when a user taps on a preference that
has an associated fragment and allows
you to customize any transitions or animations
between fragments.
There is a default implementation.
So if you don't overwrite this method,
we will still handle it for you.
But you won't have any nice transitions or animations
that you may want.
And the fragments are going to be exactly the same as before.
Again, all we need to do here is just
overwrite onCreatePreferences and set the relevant XML
resource.
And that's really it now.
Whenever a user taps on the messages or the sync
preference, the corresponding fragment
will be ultimately displayed to the user.
So to wrap some things up, we've recently released 1.1 Alpha01.
So please, if you haven't already, get it.
Try using Dynamic Summaries and other recent features.
Please forward any bugs and feature requests
to this component on issuetracker.google.com.
We've recently updated the Android Settings
Guide to make use of the AndroidX Preference library.
Please check it out for further information
on some of the things I've talked
about in this talk and some other features out
of the scope of this talk.
And we're working on Template for Android Studio,
as well as public samples demonstrating
this new library coming soon.
For more information on Android Jetpack,
you can go to developer.android.com/jetpack.
And thank you for listening.
[APPLAUSE]
[MUSIC PLAYING]