Saturday, November 2, 2013

Multiple dependencies in Android preferences

While adding a new feature to my Android app Changelog Droid I discovered the need for a checkbox preference that is dependent on two separate preferences, i.e. the preference is only enabled if two other preferences are cheked. If either one is unchecked, it should be disabled. The (simplified) hierarchy looks as follows:

  • Enable notifications
    • Enable notifications for updated apps
      • Enable detailed notifications for updated apps

Logically, the preference "Enable detailed notifications" should only be enabled, if "Enable notifications for updated apps" is checked and both should only be enabled, if "Enable notifications" is checked. I googled a bit to find out there is no built-in way to achieve this, as preferences can only have one value for the "dependency" attribute, so I created my own implementation.

My solution requires a custom preference class which extends CheckBoxPreference (but could also extend any other Preference class). This is the code for my custom class:

public class MultiDependencyCheckboxPrereference extends CheckBoxPreference {

 private final String[] dependencies;

 public MultiDependencyCheckboxPrereference(final Context context,
   final AttributeSet attrs) {
  super(context, attrs);

  final TypedArray typedArray = context.obtainStyledAttributes(attrs,
    R.styleable.MultiDependencyCheckboxPrereference);

  final String dependencyString = typedArray
    .getString(R.styleable.MultiDependencyCheckboxPrereference_dependencies);

  if (dependencyString != null) {
   dependencies = dependencyString.split(",");
  } else {
   dependencies = new String[0];
  }

  typedArray.recycle();
 }

 @Override
 protected void onAttachedToActivity() {
  super.onAttachedToActivity();

  registerDependencies();
 }

 private void registerDependencies() {
  for (final String key : dependencies) {
   final Preference preference = findPreferenceInHierarchy(key);

   if (preference != null) {

    try {
     final Class<Preference> prefClass = Preference.class;
     final Method registerMethod = prefClass.getDeclaredMethod(
       "registerDependent", Preference.class);
     registerMethod.setAccessible(true);
     registerMethod.invoke(preference, this);
    } catch (final Exception e) {
     e.printStackTrace();
    }

   }
  }

 }

}

You can see that I'm using reflection to access the method registerDependent() which sadly is private to the Preference class. This method is used for the original "dependency" attribute and we are reusing it here. The keys of the depending classes are derrived from the new attribute "dependencies" in the constructor where multiple keys can be declared, split by a comma.

You will also need to define the "dependencies" attribute in your values resource like this:
<resources>
   
    <declare-styleable name="MultiDependencyCheckboxPrereference">
        <attr name="dependencies" format="string" />
    </declare-styleable>

</resources>
Finally you can use your new preference in your preferences xml like this:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:cd="http://schemas.android.com/apk/res/your.package"
    android:key="pref_cat_notifications"
    android:title="@string/pref_cat_notification" >

 <CheckBoxPreference
        android:key="pref_notifications"
        android:title="@string/pref_notification" >
 </CheckBoxPreference>

 <CheckBoxPreference
        android:dependency="pref_notifications"
        android:key="pref_notificationsForUpdates"
        android:title="@string/pref_notificationsForUpdates" >
 </CheckBoxPreference>

 <your.package.MultiDependencyCheckboxPrereference
        android:key="pref_updated_notification_detailed"
        android:title="@string/pref_updated_notification_detailed"
        cd:dependencies="pref_notifications,pref_notificationsForUpdates" >
 </your.package.MultiDependencyCheckboxPrereference>

</PreferenceScreen>
You will need to replace "your.package" with your actual package name. Finally, you can enter multiple dependencies in the new "dependencies" attribute in your custom preference, split by a comma. In my example, the last preference is only enabled, if both other preferences are checked.

No comments:

Post a Comment