Norway


We often see Settings screen in many android apps through which you can configure the app on your choice. For example you wanna the notification sound or turn off notification messages from the app settings.

Normally people manually develop their own UI for settings and manage the values in shared preferences, not awaring the fact that android do provide APIs specific to Settings Preferences to achieve the same in a robust way. In this article we are going to learn how to implement the settings screen considering the various combinations of settings items.

android-settings-banner Android Implementing Preferences Settings Screen - wAAACwAAAAAAQABAEACAkQBADs  - Android Implementing Preferences Settings Screen

The preference API consists of multiple widget like EditTextPreference, CheckBoxPreference, ListPreference and few more to achieve the most used settings. You can also implement the advanced options like ringtone selection, sync options etc.,

1. Settings with Preference Headers

Preference Headers displays only the settings groups (headers) on the first screen and selecting a group displays the sublist. The preference headers usually used for tablets or if there are multiple settings with nested options. The benefit of using headers is, the screen will be automatically divided into two panels when the app is running on tablet.

Below is the example of app settings with preference headers.

android-settings-of-youtube-app Android Implementing Preferences Settings Screen - wAAACwAAAAAAQABAEACAkQBADs  - Android Implementing Preferences Settings Screen

1.1 Adding Settings Activity from Android Studio Templates

The Settings Activity can be added from Android Studio templates directly with all the necessary code added to activity.

In Android Studio go to File ⇒ Activity ⇒ Settings Activity. This creates necessary preferences xml resources under res ⇒ xml directory.

android-studio-settings-activity Android Implementing Preferences Settings Screen - wAAACwAAAAAAQABAEACAkQBADs  - Android Implementing Preferences Settings Screen

If you launch the Settings Activity, you will see the output as below.

preference-settings-with-headers-1 Android Implementing Preferences Settings Screen - wAAACwAAAAAAQABAEACAkQBADs  - Android Implementing Preferences Settings Screen

The Problem:

The problem with creating settings from a template is, there is no guarantee that the auto generated code always meets your requirements. With current version of Android Studio I have (v2.3), the settings activity is generated with all headers as default screen which is not necessary in my app. Even it takes more effort to customize the auto generated code. The best way is, create everything manually so that you will have more control over the output you are expecting.

So let’s start by creating new project in Android Studio and see how to add the settings manually.

2. Creating New Project

1. Create a new project in Android Studio from File ⇒ New Project and fill the project details. I gave project name as Preferences and package name as info.androidhive.settings.

2. Open strings.xml and add the below string values.

<resources>
    <string name="app_name">Preferences</string>
    <string name="action_settings">Settings</string>
    <string name="title_activity_settings">Settings</string>

    <string name="pref_title_ringtone">Ringtone</string>
    <string name="pref_ringtone_silent">Silent</string>

    <string name="default_gallery_storage">My Videos</string>
    <string name="title_auto_upload">Auto upload</string>
    <string name="summary_upload_over_wifi">Upload the videos when wifi is available</string>
    <string name="title_upload_quality">Upload Quality</string>
    <string name="summary_upload_video_quality">Specify video quality for uploads</string>
    <string name="pref_title_notifications">Notifications</string>
    <string name="summary__ringtone"> notification sound</string>
    <string name="pref_header_about">About</string>
    <string name="summary_about">We are a team of like-minded people, specialized in development of Android App Development creating new trends across the app space. Glad to Help in anyway!</string>
    <string name="app_version">3.5</string>
    <string name="summary_support">Got any queries? We are happy to help!</string>
    <string name="title_send_">Send </string>
    <string name="title_faq">FAQ</string>
    <string name="summary_faq">View frequently asked questions</string>
    <string name="url_faq">https://www.androidhive.info/-policy/</string>
    <string name="_policy"> Policy</string>
    <string name="url_">https://www.androidhive.info/-policy/</string>
    <string name="title_terms">Terms &amp; Conditions</string>
    <string name="url_terms">https://www.androidhive.info/terms-of-service/</string>
    <string name="title_version">Version</string>
    <string name="choose_email_client">Choose email client</string>
    <string name="title_gallery_storage">Default Storage</string>
    <string name="title_new_notification_sound">New message notification</string>
    <string name="title_vibrate">Vibrate</string>
    <string name="summary_vibrate">Vibrate on new notification</string>
    <string name="_upload_over_wifi">_upload_over_wifi</string>
    <string name="_gallery_name">_gallery_name</string>
    <string name="_upload_quality">_upload_quality</string>
    <string name="notifications_new_message">notifications_new_message</string>
    <string name="_notifications_new_message_ringtone">_notifications_new_message_ringtone</string>
    <string name="_vibrate">_vibrate</string>
    <string name="_send_">key_send_</string>
</resources>

3. Create new xml named arrays.xml under res ⇒ arrays.xml and add the below arrays. These arrays contains values necessary for ListPrefereces.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="pref_upload_quality_entries">
        <item>360p</item>
        <item>480p</item>
        <item>720p</item>
        <item>1080p</item>
        <item>Original</item>
    </string-array>
    <string-array name="pref_upload_quality_values">
        <item>0</item>
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
    </string-array>
</resources>

4. Create an xml named pref_main.xml under res ⇒ xml. If the xml directory is not existed, create a new folder with the name.

The preferences screen can be designed with an xml just like the layout xml we use for a view, except it will reside in xml directory. We have used below elements in the current settings screen.

> EditTextPreference – Used to collect text input from user

> CheckBoxPreference – Displays a checkbox to toggle a setting

> ListPreference – Displays a diglog with list of options to choose

> SwitchPreference – Turn on/off a setting

> RingtonePreference – Launches devices’s ringtone list in which user can select preferred sound

> Preference with an Intent action android.intent.action.VIEW – to open external browser navigating to an url

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="General">
        <EditTextPreference
            android:defaultValue="@string/default_gallery_storage"
            android:key="@string/key_gallery_name"
            android:summary="@string/default_gallery_storage"
            android:title="@string/title_gallery_storage" />

        <CheckBoxPreference
            android:defaultValue="true"
            android:key="@string/key_upload_over_wifi"
            android:summary="@string/summary_upload_over_wifi"
            android:title="@string/title_auto_upload" />

        <ListPreference
            android:defaultValue="3"
            android:dialogTitle="@string/title_upload_quality"
            android:entries="@array/pref_upload_quality_entries"
            android:entryValues="@array/pref_upload_quality_values"
            android:key="@string/key_upload_quality"
            android:summary="@string/summary_upload_video_quality"
            android:title="@string/title_upload_quality" />

    </PreferenceCategory>

    <PreferenceCategory android:title="@string/pref_title_notifications">

        <SwitchPreference
            android:defaultValue="true"
            android:key="@string/notifications_new_message"
            android:title="@string/title_new_notification_sound" />

        <RingtonePreference
            android:defaultValue="content://settings/system/notification_sound"
            android:dependency="notifications_new_message"
            android:key="@string/key_notifications_new_message_ringtone"
            android:ringtoneType="notification"
            android:summary="@string/summary_choose_ringtone"
            android:title="@string/pref_title_ringtone" />

        <SwitchPreference
            android:defaultValue="true"
            android:key="@string/key_vibrate"
            android:summary="@string/summary_vibrate"
            android:title="@string/title_vibrate" />
    </PreferenceCategory>

    <PreferenceCategory android:title="@string/pref_header_about">

        <Preference
            android:selectable="false"
            android:summary="@string/summary_about" />

        <Preference
            android:summary="@string/app_version"
            android:title="@string/title_version" />

        <Preference
            android:key="@string/key_send_feedback"
            android:summary="@string/summary_support"
            android:title="@string/title_send_feedback" />

        <!-- preference opens url in browser -->
        <Preference
            android:summary="@string/summary_faq"
            android:title="@string/title_faq">
            <intent
                android:action="android.intent.action.VIEW"
                android:data="@string/url_faq" />
        </Preference>

        <Preference android:title="@string/privacy_policy">
            <intent
                android:action="android.intent.action.VIEW"
                android:data="@string/url_privacy" />
        </Preference>

        <Preference android:title="@string/title_terms">
            <intent
                android:action="android.intent.action.VIEW"
                android:data="@string/url_terms" />
        </Preference>
    </PreferenceCategory>
</PreferenceScreen>

5. Create a class named AppCompatPreferenceActivity.java and add the below code. This class provides compatibility across all the devices and versions.

import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
 * to be used with AppCompat.
 */
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {

    private AppCompatDelegate mDelegate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getDelegate().installViewFactory();
        getDelegate().onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        getDelegate().onPostCreate(savedInstanceState);
    }

    public ActionBar getSupportActionBar() {
        return getDelegate().getSupportActionBar();
    }

    public void setSupportActionBar(@Nullable Toolbar toolbar) {
        getDelegate().setSupportActionBar(toolbar);
    }

    @Override
    public MenuInflater getMenuInflater() {
        return getDelegate().getMenuInflater();
    }

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }

    @Override
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().addContentView(view, params);
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();
        getDelegate().onPostResume();
    }

    @Override
    protected void onTitleChanged(CharSequence title, int color) {
        super.onTitleChanged(title, color);
        getDelegate().setTitle(title);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        getDelegate().onConfigurationChanged(newConfig);
    }

    @Override
    protected void onStop() {
        super.onStop();
        getDelegate().onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getDelegate().onDestroy();
    }

    public void invalidateOptionsMenu() {
        getDelegate().invalidateOptionsMenu();
    }

    private AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, null);
        }
        return mDelegate;
    }
}

6. Create new activity named SettingsActivity from File ⇒ New ⇒ Activity ⇒ Empty Activity and do the below changes.

> Extend this class from AppCompatPreferenceActivity that we have created in the above step.

> Remove setContentView() and load the MainPreferenceFragment() fragment.

> MainPreferenceFragment() contains all the necessary methods to inflate the settings xml, provides callbacks when the settings are changed.

package info.androidhive.settings;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.RingtonePreference;
import android.text.TextUtils;
import android.view.MenuItem;

public class SettingsPrefActivity extends AppCompatPreferenceActivity {
    private static final String TAG = SettingsPrefActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        // load settings fragment
        getFragmentManager().beginTransaction().replace(android.R.id.content, new MainPreferenceFragment()).commit();
    }

    public static class MainPreferenceFragment extends PreferenceFragment {
        @Override
        public void onCreate(final Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.pref_main);

            // gallery EditText change listener
            bindPreferenceSummaryToValue(findPreference(getString(R.string.key_gallery_name)));

            // notification preference change listener
            bindPreferenceSummaryToValue(findPreference(getString(R.string.key_notifications_new_message_ringtone)));

            // feedback preference click listener
            Preference myPref = findPreference(getString(R.string.key_send_feedback));
            myPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
                public boolean onPreferenceClick(Preference preference) {
                    sendFeedback(getActivity());
                    return true;
                }
            });
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            onBackPressed();
        }
        return super.onOptionsItemSelected(item);
    }

    private static void bindPreferenceSummaryToValue(Preference preference) {
        preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);

        sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
                PreferenceManager
                        .getDefaultSharedPreferences(preference.getContext())
                        .getString(preference.getKey(), ""));
    }

    /**
     * A preference value change listener that updates the preference's summary
     * to reflect its new value.
     */
    private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
        @Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            String stringValue = newValue.toString();

            if (preference instanceof ListPreference) {
                // For list preferences, look up the correct display value in
                // the preference's 'entries' list.
                ListPreference listPreference = (ListPreference) preference;
                int index = listPreference.findIndexOfValue(stringValue);

                // Set the summary to reflect the new value.
                preference.setSummary(
                        index >= 0
                                ? listPreference.getEntries()[index]
                                : null);

            } else if (preference instanceof RingtonePreference) {
                // For ringtone preferences, look up the correct display value
                // using RingtoneManager.
                if (TextUtils.isEmpty(stringValue)) {
                    // Empty values correspond to 'silent' (no ringtone).
                    preference.setSummary(R.string.pref_ringtone_silent);

                } else {
                    Ringtone ringtone = RingtoneManager.getRingtone(
                            preference.getContext(), Uri.parse(stringValue));

                    if (ringtone == null) {
                        // Clear the summary if there was a lookup error.
                        preference.setSummary(R.string.summary_choose_ringtone);
                    } else {
                        // Set the summary to reflect the new ringtone display
                        // name.
                        String name = ringtone.getTitle(preference.getContext());
                        preference.setSummary(name);
                    }
                }

            } else if (preference instanceof EditTextPreference) {
                if (preference.getKey().equals("key_gallery_name")) {
                    // update the changed gallery name to summary filed
                    preference.setSummary(stringValue);
                }
            } else {
                preference.setSummary(stringValue);
            }
            return true;
        }
    };

    /**
     * Email client intent to send support mail
     * Appends the necessary device information to email body
     * useful when providing support
     */
    public static void sendFeedback(Context context) {
        String body = null;
        try {
            body = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
            body = "nn-----------------------------nPlease don't remove this informationn Device OS: Android n Device OS version: " +
                    Build.VERSION.RELEASE + "n App Version: " + body + "n Device Brand: " + Build.BRAND +
                    "n Device Model: " + Build.MODEL + "n Device Manufacturer: " + Build.MANUFACTURER;
        } catch (PackageManager.NameNotFoundException e) {
        }
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("message/rfc822");
        intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"contact@androidhive.info"});
        intent.putExtra(Intent.EXTRA_SUBJECT, "Query from android app");
        intent.putExtra(Intent.EXTRA_TEXT, body);
        context.startActivity(Intent.createChooser(intent, context.getString(R.string.choose_email_client)));
    }
}

Now we have the settings screen implemented. We just need to add a menu item in the toolbar’s overflow menu to launch the settings.

7. Open menu_main.xml under res ⇒ menu and add the settings menu item.

8. Open MainActivity.java and launch SettingsActivity when the settings is selected from overflow menu.

package info.androidhive.settings;

import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            // launch settings activity
            startActivity(new Intent(MainActivity.this, SettingsPrefActivity.class));
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Run the project and open the settings from toolbar’s menu item.

android-settings-preferences Android Implementing Preferences Settings Screen - wAAACwAAAAAAQABAEACAkQBADs  - Android Implementing Preferences Settings Screen

I hope this article explained most of the concepts regarding preference settings. If you still have any queries, please do let me know in the comments section below.

Happy Coding 😀

References

Settings developer guide
https://developer.android.com/guide/topics/ui/settings.html

Material guidelines for Settings
https://material.io/guidelines/patterns/settings.html

Hi there! I am Founder at androidhive and programming enthusiast. My skills includes Android, iOS, PHP, Ruby on Rails and lot more. If you have any idea that you would want me to develop? Let’s talk: ravi@androidhive.info



Source link

LEAVE A REPLY

Please enter your comment!
Please enter your name here