Inner PreferenceScreen does not open with PreferenceFragmentCompat - android

My inner PreferenceScreen of PreferenceFragmentCompat is not showing, or seems to ignore tapping events.
I created MyPreferenceFragment that extends PreferenceFragmentCompat
public class MyPreferenceFragment extends PreferenceFragmentCompat {
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences);
}
}
then I changed my theme at styles.xml like
<style name="AppTheme" parent="#style/Theme.AppCompat.Light">
<item name="preferenceTheme">#style/PreferenceThemeOverlay</item>
</style>
And finally create my preferences.xml file like
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference android:title="Check Me"/>
<PreferenceScreen android:title="My Screen"> <!-- This is not opening -->
<EditTextPreference android:title="Edit text" />
</PreferenceScreen>
</PreferenceScreen>
At the build.gradle I have added both:
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.android.support:preference-v7:23.0.1'
code of the Activity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
activity_main.xml
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment"
android:name="com.mando.preferenceapp.MyPreferenceFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Testing the above code I cannot open / get into the preference screen. Am I missing something? Why this isn't working?

After spending many many hours with tries, searching and thankfully with some assistance from the creators of the support library. I've managed to make it work.
Step 1. Activity
public class MyActivity extends AppCompatActivity implements
PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
// Create the fragment only when the activity is created for the first time.
// ie. not after orientation changes
Fragment fragment = getSupportFragmentManager().findFragmentByTag(MyPreferenceFragment.FRAGMENT_TAG);
if (fragment == null) {
fragment = new MyPreferenceFragment();
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, fragment, MyPreferenceFragment.FRAGMENT_TAG);
ft.commit();
}
}
#Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat,
PreferenceScreen preferenceScreen) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
MyPreferenceFragment fragment = new MyPreferenceFragment();
Bundle args = new Bundle();
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
fragment.setArguments(args);
ft.replace(R.id.fragment_container, fragment, preferenceScreen.getKey());
ft.addToBackStack(preferenceScreen.getKey());
ft.commit();
return true;
}
}
Tips.
Do not add the fragment by xml you will have crashes on orientation changes.
Handle the recreations of activity / fragment add in onCreate so as to avoid losing your fragment when inside a preference screen.
The host activity of the fragment should implement the PreferenceFragmentCompat.OnPreferenceStartScreenCallback and recreate fragments of the same instance.
Step 2. PreferenceFragment
public class MyPreferenceFragment extends PreferenceFragmentCompat {
public static final String FRAGMENT_TAG = "my_preference_fragment";
public MyPreferenceFragment() {
}
#Override
public void onCreatePreferences(Bundle bundle, String rootKey) {
setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
Tips.
Use the method setPreferencesFromResource and take advantage of the rootKey of each screen. This way your code will be reused properly.
Keep in mind that if you have code like findPreference in your fragment it should have null checks as when you were in inner screens this will give you nothing.
The thing that is missing now is the implementation of the back arrow in the actionbar (home action) but this never works by itself ;-)
I' also created a demo app wrapping all this code you can find it on github.

Solution is to start another fragment of the same class but with different root key. No Activity actions involved.
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey){
if(getArguments() != null){
String key = getArguments().getString("rootKey");
setPreferencesFromResource(R.xml.preferences, key);
}else{
setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
#Override
public void onNavigateToScreen(PreferenceScreen preferenceScreen){
ApplicationPreferencesFragment applicationPreferencesFragment = new ApplicationPreferencesFragment();
Bundle args = new Bundle();
args.putString("rootKey", preferenceScreen.getKey());
applicationPreferencesFragment.setArguments(args);
getFragmentManager()
.beginTransaction()
.replace(getId(), applicationPreferencesFragment)
.addToBackStack(null)
.commit();
}

I did it slightly differently, I'm launching a new activity for each screen. This seems to require less hacks: no need to mess with swapping fragments and background colors. You also get activity change animation as a bonus!
public class PreferencesActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
final static private String KEY = "key";
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.preferences);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true);
if (savedInstanceState != null)
return;
Fragment p = new PreferencesFragment();
String key = getIntent().getStringExtra(KEY);
if (key != null) {
Bundle args = new Bundle();
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, key);
p.setArguments(args);
}
getSupportFragmentManager().beginTransaction()
.add(R.id.preferences, p, null)
.commit();
}
#Override public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
Intent intent = new Intent(PreferencesActivity.this, PreferencesActivity.class);
intent.putExtra(KEY, preferenceScreen.getKey());
startActivity(intent);
return true;
}
#Override public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
public static class PreferencesFragment extends PreferenceFragmentCompat implements ... {
private static final String FRAGMENT_DIALOG_TAG = "android.support.v7.preference.PreferenceFragment.DIALOG";
private String key;
#Override public void onCreatePreferences(Bundle bundle, String key) {
setPreferencesFromResource(R.xml.preferences, this.key = key);
}
// this only sets the title of the action bar
#Override public void onActivityCreated(Bundle savedInstanceState) {
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (actionBar != null) actionBar.setTitle((key == null) ? "Settings" : findPreference(key).getTitle());
super.onActivityCreated(savedInstanceState);
}
}
}
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:orientation="vertical"
android:padding="0dp"
android:id="#+id/preferences">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" />
<!-- preference fragment will be inserted here programmatically -->
</LinearLayout>

Another solution is to track the preference screens yourself and use the PreferenceFragmentCompat api
Here's the basic solution. (It doesn't cover all the edge cases, see advanced solution below)
Ensure you have configChanges="orientation" to prevent create/destroy
<activity
android:name=".MyPreferencesActivity"
android:configChanges="orientation" />
In the Activity you want to keep a Stack of PreferenceScreens and push/pop as needed
/* track the screens as a Stack */
private Stack<PreferenceScreen> preferenceScreens = new Stack<>();
// ensure your Activity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback
#Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
preferenceScreens.push(preferenceFragmentCompat.getPreferenceScreen());
preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
return true;
}
#Override
public void onBackPressed() {
if (preferenceScreens.empty()) {
super.onBackPressed();
} else {
prefsFragment.setPreferenceScreen(preferenceScreens.pop());
}
}
Optional: In your Fragment that extends PreferenceFragmentCompat, add setRetainInstance(true). (Note that without
this it will likely work also, but it 'could' break occasionally. If you set 'Don't keep Activities' to true, and
you'll see that it will get collected)
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setRetainInstance(true);
// Load the preferences from an XML resource
setPreferencesFromResource(R.xml.preferences, rootKey);
...
That's it! Except that if you want to cover edge cases...
Advanced Solution (If you set 'Don't Keep Activities to True, you'll need to ensure you can rebuild everything from savedInstanceState)
Note that the accepted answer doesn't actually preserve state.
set 'Don't Keep Activities' to True
navigate to a nested PreferenceScreen
Press home and then navigate back to the app
It 'should' still be on the Nested PreferenceScreen, but it's actually on the root one
Full Advanced Solution using PreferenceFragmentCompat api and preserving the PreferenceScreen stack
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceScreen;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Stack;
/**
* Class to Show the preference screen with Activity keeping state
* #author Aaron Vargas
*/
public class MyPreferencesActivityStateful extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
private static final String PREFERENCE_SCREENS = "PREFERENCE_SCREENS";
private PrefsFragment prefsFragment;
private Stack<PreferenceScreen> preferenceScreens = new Stack<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Display the fragment as the main content. Re-Use if possible
String tag = PrefsFragment.class.getName();
prefsFragment = (PrefsFragment) getSupportFragmentManager().findFragmentByTag(tag);
if (prefsFragment == null) prefsFragment = new PrefsFragment();
getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
prefsFragment, tag).commit();
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// rebuild preferenceScreen stack
for (String screenKey : Objects.requireNonNull(savedInstanceState.getStringArrayList(PREFERENCE_SCREENS))) {
preferenceScreens.push((PreferenceScreen) prefsFragment.findPreference(screenKey));
}
PreferenceScreen preferenceScreen = preferenceScreens.pop();
if (preferenceScreen != prefsFragment.getPreferenceScreen()) { // optimize if same
prefsFragment.setPreferenceScreen(preferenceScreen);
}
}
#Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
preferenceScreens.push(preferenceFragmentCompat.getPreferenceScreen());
preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
return true;
}
#Override
public void onBackPressed() {
// account for onRestore not getting called equally to onSave
while (preferenceScreens.contains(prefsFragment.getPreferenceScreen())) {
preferenceScreens.remove(prefsFragment.getPreferenceScreen());
}
if (preferenceScreens.empty()) {
super.onBackPressed();
} else {
prefsFragment.setPreferenceScreen(preferenceScreens.pop());
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
preferenceScreens.push(prefsFragment.getPreferenceScreen());
ArrayList<String> keys = new ArrayList<>(preferenceScreens.size());
for (PreferenceScreen screen : preferenceScreens) {
keys.add(screen.getKey());
}
outState.putStringArrayList(PREFERENCE_SCREENS, keys);
}
public static class PrefsFragment extends PreferenceFragmentCompat {
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setRetainInstance(true); // ensure in manifest - android:configChanges="orientation"
// Load the preferences from an XML resource
setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
}
You can also handle all this in your Fragment instead of the Activity. Here's a gist of that https://gist.github.com/aaronvargas/0f210ad8643b512efda4acfd524e1232

Using Navigation Component (Android Jetpack) and Kotlin it's very easy now:
class PrefsFragment : PreferenceFragmentCompat() {
private val args: PrefsFragmentArgs by navArgs()
override fun onCreatePreferences(state: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.prefs, args.rootKey)
}
override fun onNavigateToScreen(preferenceScreen: PreferenceScreen?) {
findNavController().navigate(
PrefsFragmentDirections.changeRoot(preferenceScreen!!.key)
)
}
}

Based on #squirrel Intent solution, I made it work this way. It requires even less hacking.
Activity:
import android.support.v7.app.AppCompatActivity;
public class SettingsActivity extends AppCompatActivity {
public static final String TARGET_SETTING_PAGE = "target";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SettingsFragment settingsFragment = new SettingsFragment();
Intent intent = getIntent();
if (intent != null) {
String rootKey = intent.getStringExtra(TARGET_SETTING_PAGE);
if (rootKey != null) {
settingsFragment.setArguments(Bundler.single(TARGET_SETTING_PAGE, rootKey));
}
}
getFragmentManager().beginTransaction()
.replace(android.R.id.content, settingsFragment)
.commit();
}
}
Fragment:
import android.support.v14.preference.PreferenceFragment;
public class SettingsFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = getArguments();
if (arguments != null && arguments.getString(TARGET_SETTING_PAGE) != null) {
setPreferencesFromResource(R.xml.preferences, arguments.getString(TARGET_SETTING_PAGE));
} else {
addPreferencesFromResource(R.xml.preferences);
}
}
#Override
public void onNavigateToScreen(PreferenceScreen preferenceScreen) {
Intent intent = new Intent(getActivity(), SettingsActivity.class)
.putExtra(TARGET_SETTING_PAGE, preferenceScreen.getKey());
startActivity(intent);
super.onNavigateToScreen(preferenceScreen);
}
}
It is sad you need so much hacks in the support appcompat libraries for something that works flawlessly out-of-the-box in standard android.

Alternative using Navigation component + androidx.appcomat:
https://stackoverflow.com/a/59732509/5437789
With this, you wont loose the back stack and go back to main page settings when you press back button.

Here is a simple solution from android documentation. To implement inner preference screen navigation with PreferenceFragmentCompact all you have to do is add fragment attribute to the embedded preference screen giving the fragment full path to navigate to eg. com.example.FragmentName.
Sample code:
<PreferenceCategory app:title="#string/choose_theme"
android:icon="#drawable/ic_baseline_color_lens_24">
<SwitchPreference
android:title="#string/apply_night_mode"
android:key="#string/key_enable_night_mode"/>
<PreferenceScreen
android:fragment="com.example.simbokeyboard.BlankFragment"
android:title="Custom Theme"
android:summary="#string/theme_summary">
<Preference
android:key="#string/choose_theme"
android:title="#string/choose_theme"
android:layout="#layout/theme_chooser"/>
</PreferenceScreen>
</PreferenceCategory>

Related

How does one deep link into the preferences UI

I have an Android app that has used Preferences UI. I want to be able to deep link into the specific preference option. I can't figure out how to do that.
I have set up the preferences using the tutorial as follows
https://developer.android.com/guide/topics/ui/settings
https://medium.com/#JakobUlbrich/building-a-settings-screen-for-android-part-1-5959aa49337c (Part 1)
https://medium.com/#JakobUlbrich/building-a-settings-screen-for-android-part-2-2ba63e2d7d1d
https://medium.com/#JakobUlbrich/building-a-settings-screen-for-android-part-3-ae9793fd31ec
I have set up deep linking activity using this tutorial
https://developer.android.com/training/app-links/deep-linking
I want to be able to deep link directly into the one of the preferences.
public class SettingsActivity extends AppCompatActivity
implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
FragmentFactory fragmentFactory = new AppFragmentFactory();
final FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager != null)
{
if (fragmentManager.findFragmentByTag(ROOT_FRAGMENT_TAG) == null)
{
final int containerId = R.id.detail_container;
final Fragment fragment = fragmentFactory.create(RootPreferencesFragment.class);
fragment.setArguments(savedInstanceState);
fragmentManager.beginTransaction()
.add(containerId, fragment, ROOT_FRAGMENT_TAG)
.commit();
}
}
}
}
#Override
public boolean onPreferenceStartFragment(PreferenceFragmentCompat callingFragment, Preference preference)
{
final Bundle arguments = preference.getExtras();
final Fragment fragment = fragmentFactory.create(getClassLoader(), preference.getFragment());
fragment.setArguments(arguments);
fragment.setTargetFragment(callingFragment, 0);
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_container, fragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.setBreadCrumbTitle(preference.getTitle())
.addToBackStack(preference.getKey())
.commit();
return true;
}
RootPreferencesFragment
public class RootPreferencesFragment extends PreferenceFragmentCompat implements DialogFragmentCallback
{
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey)
{
final FragmentActivity activity = Objects.requireNonNull(getActivity());
// Notifications
final Preference notificationsPreference = preferencesFactory.create(
accountCategory,
Preference.class,
RootViewModel.ACCOUNT_NOTIFICATIONS_PREFERENCE_KEY,
R.string.PushNotifications,
RootViewModel.ACCOUNT_CATEGORY_KEY);
notificationsPreference.setFragment(NotificationPreferencesFragment.class.getName());
// General Category
final PreferenceCategory generalCategory = preferencesFactory.create(
preferenceScreen,
PreferenceCategory.class,
RootViewModel.GENERAL_CATEGORY_KEY,
R.string.preferences_general_group);
// About category
final PreferenceCategory aboutCategory = preferencesFactory.create(
preferenceScreen,
PreferenceCategory.class,
RootViewModel.ABOUT_CATEGORY_KEY,
R.string.preferences_about_group);
// About
final Preference aboutPreference = preferencesFactory.create(
aboutCategory,
Preference.class,
RootViewModel.ABOUT_PREFERENCE_KEY,
R.string.preferences_about);
aboutPreference.setSummary(aboutViewModel.getAppVersion());
aboutPreference.setFragment(AboutPreferencesFragment.class.getName());
}
}
I don't know how to trigger deep link into a specific NotificationsPreference. There is only one activity, but there are three preferences.

Preference sub-screen not opening when using support.v7.preference

I am trying to implement preferences with sub-screens using AppCompatActivity and support.v7.preference
According to the docs, every PreferenceScreen within another PreferenceScreen functions as a sub-screen, and the framework will handle displaying it when clicked.
http://developer.android.com/guide/topics/ui/settings.html#Subscreens
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- opens a subscreen of settings -->
<PreferenceScreen
android:key="button_voicemail_category_key"
android:title="#string/voicemail"
android:persistent="false">
<ListPreference
android:key="button_voicemail_provider_key"
android:title="#string/voicemail_provider" ... />
<!-- opens another nested subscreen -->
<PreferenceScreen
android:key="button_voicemail_setting_key"
android:title="#string/voicemail_settings"
android:persistent="false">
...
</PreferenceScreen>
<RingtonePreference
android:key="button_voicemail_ringtone_key"
android:title="#string/voicemail_ringtone_title"
android:ringtoneType="notification" ... />
...
</PreferenceScreen>
...
</PreferenceScreen>
This works fine using native Activity, PreferenceFragment... but using AppCompatActivity and PreferenceFragmentCompat, clicking the Preference element just highlights it, but doesn't open the sub-screen.
I couldn't find anything on this reading the docs and the code... do I need to implement any additional callbacks?
EDIT: just for completeness...
This works and opens the sub-screen:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new DemoPreferenceFragment())
.commit();
}
}
static public class DemoPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
}
This doesn't work/open the sub-screen:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(android.R.id.content, new DemoPreferenceFragment())
.commit();
}
}
static public class DemoPreferenceFragment extends PreferenceFragmentCompat {
#Override
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.preferences);
}
}
}
Edit: 25/01/2016
After fiddling with support.v7.preference for a few days, I've summed up my findings here, hoping it may help others:
HowTo use support.v7.preference with AppCompat and potential drawbacks
It looks like a bug in PreferenceFragmentCompat or insufficiency of docs. It has method onNavigateToScreen which is called when you click on PreferenceScreen item.
But method getCallbackFragment() returns null by default, so you need override it in your fragment to return this. Also you need to implement PreferenceFragmentCompat.OnPreferenceStartScreenCallback.
public class SettingsFragment extends PreferenceFragmentCompat implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
public static SettingsFragment newInstance() {
return new SettingsFragment();
}
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.news_settings);
}
#Override
public Fragment getCallbackFragment() {
return this;
}
#Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
return true;
}
}
But it leads to another problem when you can't get back to your initial PreferenceScreen,
Another way is to replace fragment which is described here How to move back from Preferences subscreen to main screen in PreferenceFragmentCompat?
This is complete working example, I hope this will be helpful to someone.It covers opening the preference subscreen and moving back to main Settings screen.
I followed this issue in Android open source issue tracker --here
The official documentation is missing the documentation for loading preference subscreen—Refer here for official documentation--
The main advanced settings screen has 2 checkboxes and a disabled subscreen title(custom Pattern Settings):-
Once we check the Custom checkbox, the subscreen title is enabled.
On click of Custom pattern settings, the subscreen opens in new screen
Here is the example code with documentation:--
In res/xml/preferences.xml file:--
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:summary="Trying intro text">
<PreferenceCategory android:title="Settings">
<CheckBoxPreference
android:defaultValue="true"
android:key="defaultPress"
android:title="Default settings" />
<CheckBoxPreference
android:defaultValue="false"
android:key="customKey"
android:title="Custom" />
<PreferenceScreen
android:key="customPrefKey"
android:title="Custom Pattern Settings">
<PreferenceCategory
android:key="customSettingsKey"
android:title="Custom Settings">
<ListPreference
android:defaultValue="4"
android:entries="#array/initialClickArray"
android:entryValues="#array/initialClickValues"
android:key="initialClicks"
android:summary="initialClicksSummary"
android:title="No. Of Clicks" />
<ListPreference
android:defaultValue="5"
android:entries="#array/initialTimeArray"
android:entryValues="#array/initialTimeValues"
android:key="initialTimeKey"
android:summary="Time to complete clicks"
android:title="Time to complete" />
</PreferenceCategory>
</PreferenceScreen>
</PreferenceCategory>
</PreferenceScreen>
MainActivity.java should implement interface PreferenceFragmentCompat.OnPreferenceStartScreenCallback and then override the method-- onPreferenceStartScreen
public class MainActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
private static final String TAG = MainActivity.class.getName();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = null;
if (savedInstanceState == null) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragment = new AdvancedSettingsFragment().newInstance("Advanced Setting");
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
}
#Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat,
PreferenceScreen preferenceScreen) {
Log.d(TAG, "callback called to attach the preference sub screen");
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
AdvancedSettingsSubScreenFragment fragment = AdvancedSettingsSubScreenFragment.newInstance("Advanced Settings Subscreen");
Bundle args = new Bundle();
//Defining the sub screen as new root for the subscreen
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
fragment.setArguments(args);
ft.replace(R.id.fragment_container, fragment, preferenceScreen.getKey());
ft.addToBackStack(null);
ft.commit();
return true;
}
For the main Settings screen(fragment):-
public class AdvancedSettingsFragment extends PreferenceFragmentCompat {
private static final String TAG = AdvancedSettingsFragment.class.getName();
public static final String PAGE_ID = "page_id";
public static AdvancedSettingsFragment newInstance(String pageId) {
AdvancedSettingsFragment f = new AdvancedSettingsFragment();
Bundle args = new Bundle();
args.putString(PAGE_ID, pageId);
f.setArguments(args);
return (f);
}
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
final CheckBoxPreference customPreference = (CheckBoxPreference) findPreference("customKey");
final Preference customSettings = (Preference) findPreference("customPrefKey");
// First time loading the preference screen, we check the saved settings and enable/disable the custom settings, based on the custom check box
//get the customSettings value from shared preferences
if (getCustomSettings(getActivity())) {
customPreference.setChecked(true);
customSettings.setEnabled(true);
} else {
customPreference.setChecked(false);
customSettings.setEnabled(false);
}
customPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object selectedValue) {
Log.d(TAG, "Inside on preference change of custom checkbox selection " + selectedValue.getClass());
if ((Boolean) selectedValue) {
customSettings.setEnabled(true);
}else{
customSettings.setEnabled(false);
}
return true;
}
});
}
private boolean getCustomSettings(Context context) {
return PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("customKey", false);
}
}
and finally for the loading of subscreen:
public class AdvancedSettingsSubScreenFragment extends PreferenceFragmentCompat {
private static final String TAG = AdvancedSettingsSubScreenFragment.class.getName();
public static final String PAGE_ID = "page_id";
public static AdvancedSettingsSubScreenFragment newInstance(String pageId) {
AdvancedSettingsSubScreenFragment f = new AdvancedSettingsSubScreenFragment();
Bundle args = new Bundle();
args.putString(PAGE_ID, pageId);
f.setArguments(args);
return (f);
}
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
// rootKey is the name of preference sub screen key name , here--customPrefKey
setPreferencesFromResource(R.xml.preferences, rootKey);
Log.d(TAG, "onCreatePreferences of the sub screen " + rootKey);
}
}
One extremely important thing you need you remember:
Your PreferenceScreen must contain :
android:key="name_a_unique_key"
Otherwise, it will not work. I've spent hours with thĂ­
Overriding PreferenceFragmentCompat.OnPreferenceStartScreenCallback
and adding the following to my preference fragment saved my day
#Override
public Fragment getCallbackFragment() {
return this;
}
#Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) {
caller.setPreferenceScreen(pref);
return true;
}
My preference version is
compile 'com.android.support:preference-v7:25.0.0'
It seems Google finally decides to support this in the newly released AndroidX preference 1.1.0-alpha.
This video from Android Dev Summit covers something about preference sub screen.

Adding toolbar to PreferenceActivity via AppCompatPreferenceActivity causes overlapping

I had gone through How to add toolbars to AppCompatPreferenceActivity?
I'm using appcompat-v7:23.0.1 and support-v4:23.0.1
Before using AppCompayPreferenceActivity, my PreferenceActivity looks the following without toolbar.
Without Toolbar
public class JStockPreferenceActivity extends PreferenceActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
// Display the fragment as the main content.
JStockPreferenceFragment jStockPreferenceFragment = JStockPreferenceFragment.newInstance();
jStockPreferenceFragment.setArguments(this.getIntent().getExtras());
this.getFragmentManager().beginTransaction().replace(android.R.id.content, jStockPreferenceFragment).commit();
}
}
After using AppCompayPreferenceActivity, with some Toolbar initialization code, the outcome looks as the following
The outcome isn't as expected :-
The Toolbar overlaps with PreferenceFragment
When touching on Toolbar, the event will be consumed by PreferenceFragment. This means, when you touch on toolbar, "Table header" preference will be touched instead.
With Toolbar
public class JStockPreferenceActivity extends AppCompatPreferenceActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
// Display the fragment as the main content.
JStockPreferenceFragment jStockPreferenceFragment = JStockPreferenceFragment.newInstance();
jStockPreferenceFragment.setArguments(this.getIntent().getExtras());
this.getFragmentManager().beginTransaction().replace(android.R.id.content, jStockPreferenceFragment).commit();
}
initToolbar();
}
private void initToolbar() {
Toolbar toolbar;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
ViewGroup root = (ViewGroup) findViewById(android.R.id.list).getParent().getParent().getParent();
toolbar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.toolbar, root, false);
root.addView(toolbar, 0);
} else {
toolbar = null;
}
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
My toolbar is
toolbar.xml
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/toolbar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" >
<!-- android:elevation="4dp" is used due to http://www.google.com/design/spec/what-is-material/elevation-shadows.html#elevation-shadows-elevation-android- -->
</android.support.v7.widget.Toolbar>
I was wondering, what I had did wrong, which causes Toolbar overlapped with PreferenceFragment
After some research and getting advice from Ian Lake, I manage to make it work by
Change from AppCompatPreferenceActivity, to AppCompatActivity+PreferenceFragmentCompat
The following solution is workable for API 10 and above.
JStockPreferenceActivity.java
public class JStockPreferenceActivity extends AppCompatActivity {
#SuppressWarnings("deprecation")
#SuppressLint("NewApi")
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.jstock_preference_activity);
initToolbar();
if (savedInstanceState == null) {
// Display the fragment as the main content.
JStockPreferenceFragment jStockPreferenceFragment = JStockPreferenceFragment.newInstance();
jStockPreferenceFragment.setArguments(this.getIntent().getExtras());
this.getSupportFragmentManager().beginTransaction().replace(R.id.content, jStockPreferenceFragment).commit();
}
}
JStockPreferenceFragment.java
public class JStockPreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener, PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback {
public static JStockPreferenceFragment newInstance() {
return new JStockPreferenceFragment();
}
However, by doing so, there is a shortcoming. Your previous custom DialogPreference no longer work. To solve this, you need to
Replace DialogPreference with PreferenceDialogFragmentCompat.
Replace DialogPreference in XML with ListPreference. (I'm not exactly sure whether ListPreference as replacement is a right way. It works anyway)
Manually show PreferenceDialogFragmentCompat, in PreferenceFragmentCompat's onPreferenceDisplayDialog.
JStockPreferenceFragment.java
public class JStockPreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener, PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback {
public static JStockPreferenceFragment newInstance() {
return new JStockPreferenceFragment();
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Showing custom dialog preference.
private void showPreferenceDialogFragmentCompat(PreferenceDialogFragmentCompat preferenceDialogFragmentCompat) {
preferenceDialogFragmentCompat.setTargetFragment(this, 0);
preferenceDialogFragmentCompat.show(this.getFragmentManager(), "android.support.v7.preference.PreferenceFragment.DIALOG");
}
private void showCurrencySymbolPreferenceDialogFragmentCompat(Preference preference) {
CurrencySymbolPreferenceDialogFragmentCompat currencySymbolPreferenceDialogFragmentCompat = CurrencySymbolPreferenceDialogFragmentCompat.newInstance(preference.getKey());
showPreferenceDialogFragmentCompat(currencySymbolPreferenceDialogFragmentCompat);
}
// Showing custom dialog preference.
////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
// Callback when we are using custom dialog preference.
#Override
public Fragment getCallbackFragment() {
return this;
}
#Override
public boolean onPreferenceDisplayDialog(PreferenceFragmentCompat preferenceFragmentCompat, Preference preference) {
final String key = preference.getKey();
if (_CURRENCY_SYMBOL_OPTIONS.equals(key)) {
showCurrencySymbolPreferenceDialogFragmentCompat(preference);
return true;
}
return false;
}
// Callback when we are using custom dialog preference.
////////////////////////////////////////////////////////////////////////////////////////////////
CurrencySymbolPreferenceDialogFragmentCompat.java
public class CurrencySymbolPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat {
public CurrencySymbolPreferenceDialogFragmentCompat() {
}
public static CurrencySymbolPreferenceDialogFragmentCompat newInstance(String key) {
CurrencySymbolPreferenceDialogFragmentCompat fragment = new CurrencySymbolPreferenceDialogFragmentCompat();
Bundle b = new Bundle(1);
b.putString("key", key);
fragment.setArguments(b);
return fragment;
}
#Override
public void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
...
this.getPreference().setSummary(getSummary());
...
}
}
}
preferences.xml
<android.support.v7.preference.ListPreference
android:title="#string/preference_currency_symbol_title"
android:key="_CURRENCY_SYMBOL_OPTIONS" />
Please note that, you need to add the following item in your theme.
<item name="preferenceTheme">#style/PreferenceThemeOverlay</item>
Some bugs regarding theme
However, the theme-ing isn't perfect yet - PreferenceFragmentCompat requires preferenceTheme to be set
This is a known issues : https://code.google.com/p/android/issues/detail?id=183376
There is proposed workaround https://stackoverflow.com/a/32108439/72437 But, the workaround will only work for v14 and above, not v7 and above.

How to move back from Preferences subscreen to main screen in PreferenceFragmentCompat?

I am trying to implement a Settings screen using PreferenceFragmentCompat. My preference xml has a preference subscreen like this:
preferences.xml
<CheckBoxPreference
android:defaultValue="false"
android:key="#string/pref_sound_key"
android:summary="#string/pref_sound_summary"
android:title="#string/pref_sound_title" />
<PreferenceScreen android:title="Inner Screen">
<CheckBoxPreference
android:defaultValue="true"
android:key="#string/key_1"
android:title="#string/title_1" />
<CheckBoxPreference
android:defaultValue="true"
android:key="#string/key_1"
android:title="#string/title_1" />
<CheckBoxPreference
android:defaultValue="true"
android:key="#string/key_2"
android:title="#string/title_2" />
<CheckBoxPreference
android:defaultValue="true"
android:key="#string/key_3"
android:title="#string/title_3" />
</PreferenceScreen>
</PreferenceScreen>
Preference Main Screen
Now, in the app, the subscreen does not open until I implement PreferenceFragmentCompat.OnPreferenceStartScreenCallback interface in parent activity, as specified in PreferenceFragmentCompat doc.
MainActivity.java
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat,
PreferenceScreen preferenceScreen) {
preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
return true;
}
Here's where the problem arises. On implementing the interface, the subscreen opens, but then there is no way I can find to move back to first screen.
Preference Subscreen
Pressing back key closes the app.
Is there any way I can put a back arrow on app bar so that pressing it will bring the main screen back?
By using setPreferenceScreen you are setting the root preference screen to the sub preference screen which is preventing you from having a hierarchy of preference screens to navigate back through.
I suggest that you treat each PreferenceScreen as a Fragment and add a new Fragment when you navigate into a sub screen.
#Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat,
PreferenceScreen preferenceScreen) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
MyPreferenceFragment fragment = new MyPreferenceFragment();
Bundle args = new Bundle();
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
fragment.setArguments(args);
ft.add(R.id.fragment_container, fragment, preferenceScreen.getKey());
ft.addToBackStack(preferenceScreen.getKey());
ft.commit();
return true;
}
MyPreferenceFragment
public class MyPreferenceFragment extends AppPreferenceFragment {
public static final String FRAGMENT_TAG = "my_preference_fragment";
public MyPreferenceFragment() {
}
#Override
public void onCreatePreferences(Bundle bundle, String rootKey) {
setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
AppPreferenceFragment
public abstract class AppPreferenceFragment extends PreferenceFragmentCompat {
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Set the default white background in the view so as to avoid transparency
view.setBackgroundColor(
ContextCompat.getColor(getContext(), R.color.background_material_light));
}
}
That way when you press the back button each Fragment will be popped from the stack.
For more information see this GitHub project
I have added a complete working example with screenshots and code snippets here in this post, I hope this will be helpful to someone.
It covers following scenarios:-- 1) A main setting screen with two checkboxes and a subscreen title. 2) On click of subscreen title, the new preference subscreen opens. 3) On back pressed, the control goes to main Settings screen. So the back press is handled properly.
The MainActivity looks like this(overridden onPreferenceStartScreen method handles the opening of new subscreen in a new window):--
public class MainActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
private static final String TAG = MainActivity.class.getName();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = null;
if (savedInstanceState == null) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragment = new AdvancedSettingsFragment().newInstance("Advanced Setting");
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
}
#Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat,
PreferenceScreen preferenceScreen) {
Log.d(TAG, "callback called to attach the preference sub screen");
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
AdvancedSettingsSubScreenFragment fragment = AdvancedSettingsSubScreenFragment.newInstance("Advanced Settings Subscreen");
Bundle args = new Bundle();
//Defining the sub screen as new root for the subscreen
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
fragment.setArguments(args);
ft.replace(R.id.fragment_container, fragment, preferenceScreen.getKey());
ft.addToBackStack(null);
ft.commit();
return true;
}
and finally the in subscreen fragment setPreferencesFromResource(R.xml.preferences, rootKey); handles the attachment of subscreen to the rootkey.
public class AdvancedSettingsSubScreenFragment extends PreferenceFragmentCompat {
private static final String TAG = AdvancedSettingsSubScreenFragment.class.getName();
public static final String PAGE_ID = "page_id";
public static AdvancedSettingsSubScreenFragment newInstance(String pageId) {
AdvancedSettingsSubScreenFragment f = new AdvancedSettingsSubScreenFragment();
Bundle args = new Bundle();
args.putString(PAGE_ID, pageId);
f.setArguments(args);
return (f);
}
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
// rootKey is the name of preference sub screen key name , here--customPrefKey
setPreferencesFromResource(R.xml.preferences, rootKey);
Log.d(TAG, "onCreatePreferences of the sub screen " + rootKey);
}
}

Toolbar is hidden in nested PreferenceScreen

I use PreferenceFragment in ActionBarActivity from support-v7 library.
In the Activity I have Toolbar. Everything goes okay, until I open a nested PreferenceScreen.
In the opened screen the Toolbar is hidden.
Maybe somebody know a workaround for this issue?
Preferences xml-file:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceCategory android:title="Main category" >
<EditTextPreference
android:defaultValue="defaultValue"
android:key="key_global_setting"
android:title="Global title" />
</PreferenceCategory>
<PreferenceCategory android:title="Nested screens" >
<PreferenceScreen
android:persistent="false"
android:title="#string/settings_facility_title" >
<CheckBoxPreference
android:defaultValue="false"
android:key="nested_screen_1_1"
android:title="Nested screen 1.1 check box" />
<CheckBoxPreference
android:defaultValue="true"
android:key="nested_screen_1_2"
android:title="Nested screen 1.2 check box" />
</PreferenceScreen>
<PreferenceScreen
android:persistent="false"
android:title="#string/settings_menu_screen_title" >
<CheckBoxPreference
android:defaultValue="true"
android:key="nested_screen2"
android:title="Nested screen 2 check box" />
</PreferenceScreen>
</PreferenceCategory>
</PreferenceScreen>
Activity layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".SettingsScreen" >
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
style="#style/Toolbar" />
<FrameLayout
android:id="#+id/contentSettings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
I found the solution on my own. I used a small work-around of all this nested PreferenceScreen's. I simply made a separation to different xml-preference files, created an additional Fragment which extends PreferenceFragment and there I show an appropriate nested preference screen.
Maybe somebody would found this useful.
Github sources link.
Some code examples below:
main_preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceCategory android:title="Main category" >
<EditTextPreference
android:defaultValue="defaultValue"
android:key="key_global_setting"
android:title="Global title" />
</PreferenceCategory>
<PreferenceCategory android:title="Nested screens" >
<Preference
android:key="NESTED_KEY1"
android:persistent="false"
android:title="Nested screen #1" />
<Preference
android:key="NESTED_KEY2"
android:persistent="false"
android:title="Nested screen #2" />
</PreferenceCategory>
</PreferenceScreen>
nested_screen1_preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="Nested screen #1" >
<CheckBoxPreference
android:defaultValue="false"
android:key="nested_screen_1_1"
android:title="Nested screen 1.1 check box" />
<CheckBoxPreference
android:defaultValue="true"
android:key="nested_screen_1_2"
android:title="Nested screen 1.2 check box" />
</PreferenceScreen>
nested_screen2_preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="Nested screen #2">
<CheckBoxPreference
android:defaultValue="true"
android:key="nested_screen2"
android:title="Nested screen 2 check box" />
</PreferenceScreen>
SettingsActivity.java
public class SettingsActivity extends ActionBarActivity implements MyPreferenceFragment.Callback {
private static final String TAG_NESTED = "TAG_NESTED";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if (toolbar != null) {
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.add(R.id.contentSettings, new MyPreferenceFragment())
.commit();
}
}
#Override
public void onBackPressed() {
// this if statement is necessary to navigate through nested and main fragments
if (getFragmentManager().getBackStackEntryCount() == 0) {
super.onBackPressed();
} else {
getFragmentManager().popBackStack();
}
}
#Override
public void onNestedPreferenceSelected(int key) {
getFragmentManager().beginTransaction().replace(R.id.contentSettings, NestedPreferenceFragment.newInstance(key), TAG_NESTED).addToBackStack(TAG_NESTED).commit();
}
}
MyPreferenceFragment.java
// The main preference fragment class
public class MyPreferenceFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener {
private Callback mCallback;
private static final String KEY_1 = "NESTED_KEY1";
private static final String KEY_2 = "NESTED_KEY2";
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof Callback) {
mCallback = (Callback) activity;
} else {
throw new IllegalStateException("Owner must implement Callback interface");
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.main_preferences);
// add listeners for non-default actions
Preference preference = findPreference(KEY_1);
preference.setOnPreferenceClickListener(this);
preference = findPreference(KEY_2);
preference.setOnPreferenceClickListener(this);
}
#Override
public boolean onPreferenceClick(Preference preference) {
// here you should use the same keys as you used in the xml-file
if (preference.getKey().equals(KEY_1)) {
mCallback.onNestedPreferenceSelected(NestedPreferenceFragment.NESTED_SCREEN_1_KEY);
}
if (preference.getKey().equals(KEY_2)) {
mCallback.onNestedPreferenceSelected(NestedPreferenceFragment.NESTED_SCREEN_2_KEY);
}
return false;
}
public interface Callback {
public void onNestedPreferenceSelected(int key);
}
}
NestedPreferencesFragment.java
public class NestedPreferenceFragment extends PreferenceFragment {
public static final int NESTED_SCREEN_1_KEY = 1;
public static final int NESTED_SCREEN_2_KEY = 2;
private static final String TAG_KEY = "NESTED_KEY";
public static NestedPreferenceFragment newInstance(int key) {
NestedPreferenceFragment fragment = new NestedPreferenceFragment();
// supply arguments to bundle.
Bundle args = new Bundle();
args.putInt(TAG_KEY, key);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checkPreferenceResource();
}
private void checkPreferenceResource() {
int key = getArguments().getInt(TAG_KEY);
// Load the preferences from an XML resource
switch (key) {
case NESTED_SCREEN_1_KEY:
addPreferencesFromResource(R.xml.nested_screen1_preferences);
break;
case NESTED_SCREEN_2_KEY:
addPreferencesFromResource(R.xml.nested_screen2_preferences);
break;
default:
break;
}
}
}
Here comes my solution, which is inspired by the original answer but not that complicated. Maybe it'll help someone...
layout/settings.xml:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
layout="#layout/toolbar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/content"
android:layout_below="#+id/toolbar"/>
</RelativeLayout>
Classes:
public class SettingsActivity extends ActionBarActivity {
#Override
protected void onCreate( Bundle savedInstanceState ) {
setContentView( R.layout.settings );
super.onCreate( savedInstanceState );
initializeSupportActionBar();
getFragmentManager().beginTransaction().replace( R.id.content, new MainFragment() ).commit();
}
#Override
public void onBackPressed() {
if( !getFragmentManager().popBackStackImmediate() ) super.onBackPressed();
}
}
public class MainFragment extends PreferenceFragment {
public MainFragment() {}
#Override
public void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
addPreferencesFromResource( R.xml.pref_main );
// "nested" is the <Preference android:key="nested" android:persistent="false"/>`
findPreference( "nested" ).setOnPreferenceClickListener( new OnPreferenceClickListener() {
#Override public boolean onPreferenceClick( Preference preference ) {
getFragmentManager().beginTransaction().replace( R.id.content, new NestedFragment() ).addToBackStack( NestedFragment.class.getSimpleName() ).commit();
return true;
}
} );
}
public class NestedFragment extends PreferenceFragment {
...
}
I tested it on 4.3 and 5.0.2 and no limitation on nesting levels applies
In my solution you only need one AppCompatActivity and one PreferenceFragement, but several XML files, each having only one level of PreferenceScreens.
XML file list
top level PreferenceScreen
second level PreferenceScreen 0
second level PreferenceScreen 1
second level PreferenceScreen 2
...
This code is for one sub-level (for simplicity and to get the idea), but you can easily extend it to have arbitrary sub-levels of PreferenceScreens.
SettingsFragment.java
public class SettingsFragment extends PreferenceFragment
{
private int xmlId;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
xmlId = R.xml.preferences;
addPreferencesFromResource(xmlId);
}
public void changePrefScreen(int xmlId, int titleId)
{
this.xmlId = xmlId;
((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(getActivity().getResources().getString(titleId));
getPreferenceScreen().removeAll();
addPreferencesFromResource(xmlId);
}
// will be called by SettingsActivity (Host Activity)
public void onUpButton()
{
if(xmlId == R.xml.preferences) // in top-level
{
// Switch to MainActivity
Intent intent = new Intent(getActivity(), MainActivity.class);
startActivity(intent);
}
else // in sub-level
{
changePrefScreen(R.xml.preferences, R.string.settings);
}
}
#Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)
{
String key = preference.getKey();
//
// Top level PreferenceScreen
//
if(key.equals("top_key_0"))
{
changePrefScreen(R.xml.download_preference_screen, R.string.download_database); // descend into second level
}
// ...
//
// Second level PreferenceScreens
//
if (key.equals("second_level_key_0"))
{
// do something...
}
// ...
}
SettingsActivity.java
public class SettingsActivity extends AppCompatActivity
{
SettingsFragment settingsFragment;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
settingsFragment = new SettingsFragment();
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
.replace(android.R.id.content, settingsFragment)
.commit();
}
//
// Handle what happens on up button
//
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case android.R.id.home:
settingsFragment.onUpButton();
return true;
}
return true;
}
// ...
}
Technically it should work for all Android versions for which the PreferenceFragment is available.
As the issue comes from the part that you are still in the same activity/fragment and the nested pref screen is just a dialog you can do the following:
You can set preference click listener
Get the root view from the dialog:
(PreferenceScreen)preference).getDialog().getWindow()
.getDecorView().getRootView());
Recursively search until find a stub view (there is one, unfortunately I do not know the android.R.id.xxxxx) and set what layout you need as title which will look like the toolbar(You can inflate toolbar):
private Toolbar toolbar;
public void findViewStub(ViewGroup viewGroup) {
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = viewGroup.getChildAt(i);
if( childView instanceof ViewStub){
((ViewStub)childView).setLayoutResource(R.layout.your_title_layout);
toolbar = ((ViewStub)childView).inflate();
}
if (childView instanceof ViewGroup) {
findViewStub((ViewGroup) childView);
}
}
}
toolbar.setNavigationIcon();
toolbar.setNavigationOnClickListener();
toolbar.setTitle();
In the layout you can put only a toolbar. And set the back icon. Register for click on it and having reference to the fragment, on click you can dismiss the dialog. You have set title and etc.

Categories

Resources