Monday, May 15, 2017

Android App Settings with Preference Fragment Compat

In this post, we will make Android app settings with new Style.
We use PreferenceFragmentCompat.

Why Preference fragment compact? 
Because by using this we can show toolbar on settings activity easily.
Before starting to take a look at documentation click here.

So we need a separate fragment class. Settings is an activity just like others but we implement fragment here.
 Ok, that's our today goal.
Let's start.
Create an Activity name Settings Activity -

public class SettingsActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_settings);

  if (getSupportActionBar() != null) {
  getSupportActionBar().setDisplayHomeAsUpEnabled(true);
     }

  }

Now set it on Manifest.XML

<activity
  android:name=".settings.SettingActivity"
  android:label="@string/action_settings" />

Note: don't set-


android:parentActivityName=".MainActivity"

if you set this, after pressing the back arrow button on the toolbar it's launch MainActivity again. Now add dependencies to your build.gradle file

compile 'com.android.support:preference-v7:25.3.1'

After that Create a Fragment Class Name SettingFragment.java and extend it to PreferenceFragmentCompat and implements. SharedPreferences.OnSharedPreferenceChangeListener Let me Show-

public class SettingsFragment extends PreferenceFragmentCompat implements
  SharedPreferences.OnSharedPreferenceChangeListener{

  @Override
  public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
  
  }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

  }

Now add a method on this class name setPreferenceSummery. If we use ListPreference then we set to list selected value. for doing this, add this code-

private void setPreferenceSummery(Preference preference,Object value){

  String stringValue = value.toString();

  if (preference instanceof ListPreference){
  // For list preferences, look up the correct display value in
  // the preference's 'entries' list (since they have separate labels/values).
  ListPreference listPreference = (ListPreference) preference;
  int prefIndex = listPreference.findIndexOfValue(stringValue);
  //same code in one line
  //int prefIndex = ((ListPreference) preference).findIndexOfValue(value);

  //prefIndex must be is equal or garter than zero because
  //array count as 0 to ....
  if (prefIndex >= 0){
  listPreference.setSummary(listPreference.getEntries()[prefIndex]);
  }
  } else {
  // For other preferences, set the summary to the value's simple string representation.
  preference.setSummary(stringValue);
     }
 }

Note: For understanding code, you see comment on the code.

Now time the fulfill the methods onCreatePreferences.
In this methods, we add our XML file for settings option. But we don't create yet. So it's show error status. But don't worry we will add this later. Code-

@Override
  public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
  // Add 'general' preferences, defined in the XML file
  addPreferencesFromResource(R.xml.pref_general);

  SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();

  PreferenceScreen preferenceScreen = getPreferenceScreen();

  int count = preferenceScreen.getPreferenceCount();

  for (int i = 0; i < count ; i++) {
  Preference p = preferenceScreen.getPreference(i);
  if (!(p instanceof CheckBoxPreference)) {
  String value = sharedPreferences.getString(p.getKey(), "");
  setPreferenceSummery(p, value);
       }
     }
  }

Now time to work with onSharedPreferenceChanged-

@Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

  Preference preference = findPreference(key);

  if (preference != null) {

  if (!(preference instanceof CheckBoxPreference)) {
  String value = sharedPreferences.getString(preference.getKey(), "");
  setPreferenceSummery(preference, value);
        }
     }
  }


Optional code: 
if you want to use the content provider to access database then you set a notification for change value-
Activity activity = getActivity();
activity.getContentResolver().notifyChange(CONTENT_URI, null);
After doing that we should register and unregister the shared preference listener with activity life cycle.

Let's do this-
//register and unregister on lifecycle
  @Override
  public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  getPreferenceScreen().getSharedPreferences()
  .registerOnSharedPreferenceChangeListener(this);
  }

  @Override
  public void onDestroy() {
  super.onDestroy();
  getPreferenceScreen().getSharedPreferences()
  .unregisterOnSharedPreferenceChangeListener(this);
  }


ok. our fragment is ready to use.(don't worry about the error, we will fix this letter, for now just ignore it.)
Now we add the toolbar back arrow code.
so that it's come back the previous activity. For more details use code comment-

@Override
    public boolean onOptionsItemSelected(MenuItem item) {

        /*
         * Normally, calling setDisplayHomeAsUpEnabled(true) 
         * (we do so in onCreate here) as well as
         * declaring the parent activity in the 
         * AndroidManifest is all that is required to get the
         * up button working properly. However, in this case, 
         * we want to navigate to the previous
         * screen the user came from when the up 
         * button was clicked, rather than a single
         * designated Activity in the Manifest.
         *
         * We use the up button's ID (android.R.id.home) 
         * to listen for when the up button is
         * clicked and then call onBackPressed 
         * to navigate to the previous Activity when this happens.
         */

        int id = item.getItemId();

        if (id == android.R.id.home){
            NavUtils.navigateUpFromSameTask(this);
        }

        return super.onOptionsItemSelected(item);
    }

Now in this part, we create XML code.

Are you tired of seeing the error? 
Now time to fix it. we create an XML file name pref_general.xml.
Let's add-
In our settings screen, we just add two preference,

  • List Preference,
  •  Checkbox preference. 

our list preference for text size. so we need to create two arrays on arrays.XML one is for label and others is for value. but we need to add string first (I provide all string for both preferences).

Strings-
<!-- for settings -->
  <!-- ***************START************* -->
  <!-- text size -->
  <string name="sTextSmallLabel">Small</string>
  <string name="sTextMediumLabel">Medium</string>
  <string name="sTextModerateLabel">Moderate</string>
  <string name="sTextLargeLabel">Large</string>
  <string name="sTextExtraLargeLabel">Extra Large</string>

  <!-- text size value-->
  <string name="sTextSmallValue">15</string>
  <string name="sTextMediumValue">17</string>
  <string name="sTextModerateValue">20</string>
  <string name="sTextLargeValue">23</string>
  <string name="sTextExtraLargeValue">26</string>

  <string name="textSizeKey" translatable="false">textSize</string>
  <string name="textSizeLabel">Text Size</string>

  <string name="switchKey">SwitchKey</string>
  <string name="switchLabel">Night Mode</string>
  <string name="switchON">Night Mode ON</string>
  <string name="switchOFF">Night Mode OFF</string>

  <!-- ***************FINISH************* -->

Now add arrays-

<resources>

  <string-array name="textSizeLabel">
  <item>@string/sTextSmallLabel</item>
  <item>@string/sTextMediumLabel</item>
  <item>@string/sTextModerateLabel</item>
  <item>@string/sTextLargeLabel</item>
  <item>@string/sTextExtraLargeLabel</item>
  </string-array>

  <string-array name="textSizeValue">
  <item>@string/sTextSmallValue</item>
  <item>@string/sTextMediumValue</item>
  <item>@string/sTextModerateValue</item>
  <item>@string/sTextLargeValue</item>
  <item>@string/sTextExtraLargeValue</item>
  </string-array>


</resources>

Note my full arrays.xml. Ok, that's enough waiting now add pref_general.xml


<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <ListPreference
  android:defaultValue="@string/sTextModerateValue"
  android:entries="@array/textSizeLabel"
  android:entryValues="@array/textSizeValue"
  android:key="@string/textSizeKey"
  android:title="@string/textSizeLabel"/>

  <CheckBoxPreference
  android:defaultValue="false"
  android:key="@string/switchKey"
  android:summaryOff="@string/switchOFF"
  android:summaryOn="@string/switchON"
  android:title="@string/switchLabel"/>

</PreferenceScreen>

Just one more work to do left to complete Settings-
we need to update activity_settings. we need to set a fragment to on this XML file. Let's add this and this fragment will show our new settings in the settings.

<fragment
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/weather_settings_fragment"
    android:name="packageName.settings.SettingFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

Ok, that's . we finish our settings.

oh! I just forget to change the theme of styles.XML. Let's add on your main app theme.

<!-- Base application theme. -->
  <style name="AppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
  <!-- must add this line or app will crash -->
  <item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
  </style>

Note: if you don't add this line your app will crash. Be careful. Now you launch this SettingsActivity.java class with intent from your desired class.

See-
settings

It is single preference screen app. But If you want to add multiple screens, follow this post- Android Settings Series

That's all today.
Hope you can use this option for your app.

Happy coding

About Author:

I am Shudipto Trafder,
Since 2015, I write android code. I love to implement new ideas. I use java and also kotlin. Now I am learning Flutter. I love to learn new things and also love to share. And that is the main reason to run this blog.


Let's Get Connected: Facebook Twitter Linkedin Github

No comments :