Shared Preferences
Topics covered: Data Persistence implementation, java
Profiles:
Linkedin: https://www.linkedin.com/in/nikitha-gullapalli/
Github: https://github.com/nikitha2/SunshineApp.git
SharedPreferences- This class gives you an object which points to a local file that will be used to store the data through the API. These files are usually stored inside the App Data subdirectory of your application’s installation location.
SharedPreferences are intended to save Key-Value pairs in storage and Android doesn’t recommend saving confidential information in shared preferences without proper security.
Android leverages the functionalities of XML to save these key-value pairs easily in these files. SharedPreferences files can be either private or shared among other applications.
In this article, we will look into how to implement data persistence in android app development using java. The video below shows the settings screen we are trying to implement.
Implementation
- Create an XML with preference layout
XML is used to create a layout for the preferences. We can use this XML to place different preferences in the desired location on-screen and provide a key for each of them.
we can have different preferences based on the need like SwitchPreference, ListPreference, CheckBoxPreference, etc.
We can future have a hierarchy of preferences. Check on the link for more information:https://developer.android.com/reference/android/preference/PreferenceScreen
<?xml version="1.0" encoding="utf-8"?><PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"> <SwitchPreference
android:key="@string/units_key"
android:title="@string/units"
android:defaultValue="@bool/pref_show_default"
android:summaryOn="@string/celsius"
android:summaryOff="@string/fahrenheit"
android:textSize="@dimen/textSizePref"/> <ListPreference
android:defaultValue="@string/Albany"
android:entries="@array/pref_location_option_labels"
android:entryValues="@array/pref_location_option_values"
android:key="@string/location_key"
android:title="@string/location_title"
android:summary="@string/Albany"/></PreferenceScreen>
Note we are using an array to set entries and entry values as shown below.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="pref_location_option_labels">
<item>@string/Albany</item>
<item>@string/Albuquerque</item>
<item>@string/Anchorage</item>
</array>
<array name="pref_location_option_values">
<item>@string/Albany</item>
<item>@string/Albuquerque</item>
<item>@string/Anchorage</item>
</array>
</resources>
2. Create a fragment class and inflate the XML
The code below will inflate the settingspreferrences XML file on creating the fragment. Now we need to place the fragment on the activity so that it can be displayed on the UI.( as we know an Activity is an application component that provides a screen with UI)
public class settingspreferencefragment extends PreferenceFragmentCompat implements OnSharedPreferenceChangeListener,Preference.OnPreferenceChangeListener{@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settingspreferrences);
}
}
3. Create an activity say SettingsActivity. I will have all my preferences settings in this activity. So we need to place the fragment we created on this activity.
There are 2 ways to place the fragment onto this activity. I can do it programmatically or through the layout.
a. activity_settings.xml- if through the layout.
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_settings"
android:name="com.nikitha.android.sunshineapp
.settingspreferencefragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
and SettingsActivity.java just call set the layout onCreate.
public class SettingsActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);// creates an up arrow in toolbar to goto previous activity
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
b. SettingsActivity.java — if through the programmatically.
public class SettingsActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
// creates an up arrow in toolbar to goto previous activity
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);// Setting fragment to activity
getSupportFragmentManager().beginTransaction()
.replace(R.id.settings_container,
new settingspreferencefragment())
.commit();
}
}
activity_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"/>
Awesome now at end of step 4 we have an activity with all the preference. The screen should look like the figure below.
4. Now we have an activity with the preference screen. If SettingsActivity is the main activity it will be loaded on starting the app, but usually, settings screen is not the main screen of the app. So we need to create a way for us to navigate to SettingsActivity.
Here we have a MainActivity that is loaded on starting the app. We created a settings menu item. On clicking on setting in the menu we want to navigate to SettingsActivity.
Menu implementation is out of scope for this article. I will create a new article for it.
Clicking on settings in the menu in MainActivity.java SettingsActivity is called using intents using the code below. Here we use explicit intent as we want to call the SettingsActivity every time the settings button is called and it is class inside the call.
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch(item.getItemId()){
case R.id.activity_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
}
return true;
}
Great, now we should be able to navigate to settings activity on clicking settings menu in the main screen
5. What we need now is to be able to reflect the options selected in the settings in our app.
To achieve this we need to know when a preference is changed, what preference is changed and what is the changed value.
Android provided us with pre implemented class to make our lives simple. So simply implements SharedPreferences.OnSharedPreferenceChangeListener. in the activity, you want to listen to the changes to as shown below. (in our case we want to listen to the changes in the preferences in the mainActivity so that I can make changes)
Note: make sure you register your OnSharedPreferenceChangeListener when activity is created so that it knows it needs to hear for preference changes in the SharedPreference file and unregister on the destroying of the activity for better use of memory.
On implementing SharedPreferences.OnSharedPreferenceChangeListener we are prompted to implement the method onSharedPreferenceChanged. This method is called every time there is a change in the SharedPreferences file. (This is the file where all our preferences are saved in key-value pairs). You can use sharedpreferences and key values to make specific changes in the app. For the purpose of explanation, I am setting my variable preferencesChanged to true and every time my activity starts- if preferencesChanged is true I am loading my data from the web using loaders.
public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
static boolean preferencesChanged=false;@Override
public void onDestroy() {
super.onDestroy();
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this);
}protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this);
}@Override
public void onSharedPreferenceChanged(SharedPreferences
sharedPreferences, String key) {
if(sharedPreferences !=null){
preferencesChanged=true;
}
}@Override
protected void onStart() {
super.onStart();
if(preferencesChanged){
Log.d(TAG, "onStart: preferences were updated");
try {
loadDataWithLoader();
preferencesChanged=false;
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
}
Now the app is expected to look something like below. Once the preferences are updated we see that mainActivity screen reloads the data as implemented.
Now the app is expected to look something like this
If you observe now when we make changes to the preferences in the settings activity and navigate back to mainActivity data is reloaded but in the settings, the screen summary does not reflect the option selected for each screenPreference.
Note: Summary is the tag we have for each screenPreference so that we know what is the value currently for the preference. if we make some change to the preference we expect the summary to reflect the latest option.
6. Summary updating is to be done in the fragment because every time we call this fragment in any activity we want this functionality to exist.
Implement OnSharedPreferenceChangeListener to know if preferences are changed. This will prompt you to implement a method onSharedPreferenceChanged. Here we are basically checking if preference changed is not null and if its an instance of Listview, get the value and find its index. Now we want to set the label to the summary. So we use the index to get the label in the index and set it to the summary.
Note:
-Do not forget to register and unregister the listener.
-Here we used getPreferenceScreen to get the preferences
-We are changing only listview summary as SwitchPreference summary is already taken care of in the layout
-OnCreate we go through each preference on the screen and set the summary
public class settingspreferencefragment
extends PreferenceFragmentCompat
implements OnSharedPreferenceChangeListener,
Preference.OnPreferenceChangeListener{
@Override
public void onCreatePreferences(Bundle savedInstanceState,
String rootKey) {
addPreferencesFromResource(R.xml.settingspreferrences);
SharedPreferences sharedPreferences =
getPreferenceScreen().getSharedPreferences();
PreferenceScreen prefScreen = getPreferenceScreen();
int count = prefScreen.getPreferenceCount();
for (int i = 0; i < count; i++) {
Preference p = prefScreen.getPreference(i);
// You don't need to set up preference summaries for
checkbox preferences because they are already set up in
xml using summaryOff and summary On
if ((p instanceof ListPreference)) {
String value = sharedPreferences
.getString(p.getKey(), "");
setPreferenceSummary(p, value);
System.out.println("-----------"+value);
}
}
} @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onDestroy() {
super.onDestroy();
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences
sharedPreferences, String key) {
Preference preference = findPreference(key);
if (null != preference) {
// Updates the summary for the preference
if ((preference instanceof ListPreference)) {
String value = sharedPreferences.
getString(preference.getKey(), "");
setPreferenceSummary(preference, value);
}
}
} private void setPreferenceSummary(Preference preference,
String value) {
if (preference instanceof ListPreference) {
// For list preferences, figure out the label of the selected value
ListPreference listPreference = (ListPreference) preference;
int prefIndex = listPreference.findIndexOfValue(value);
if (prefIndex >= 0) {
// Set the summary to that label
listPreference.setSummary(listPreference
.getEntries()[prefIndex]);
}
}
}}
Conclusion:
Awsome now we a functioning settings screen that is updating the summary and changes made in the settings are being seen in the app. Thank you for reading. Please let me know any flaws in the article and I would love to fix them.
References:
https://zocada.com/using-shared-preferences-to-save-data-locally-in-android/
https://www.tutorialspoint.com/android/android_sqlite_database.htm
https://www.futurelearn.com/courses/secure-android-app-development/0/steps/21599
https://www.udacity.com/