Java Question Need some help with the PreferencesActivity library

corwin42

Expert
Licensed User
Longtime User
I'm trying to add the possibility to call Intents from a Preference Item.

The Problem I have is that the B4APreference class implements Serializeable and since I want to store an Intent in there this is not possible. Then I tried to change Serializeable to Parcelable since Intents are parcelable Objects. Unfortunately for this you have to implement some additional Methods and I don't know if I have done it correctly.

The class compiles correctly but if I try to use the Library I get a NullPointerException. I think this is because I don't have implemented the CREATOR methods correctly because of the createPreference() abstract method.

I think my Java skills are too bad to solve this. I provide the full source of the class here.

Any Ideas on this? Or is there another way to do this?

B4X:
// Changes to original PreferencesActivity by Erel:
// v1.0
// - Checkbox can show On and Off summary
// - Dependencies to other entries
// - Second Listview type with Map as key/entry pairs
// - AddPassword()
// - AddRingtone()
//
// v1.01
// - Bugfix for FC with nested PreferencesScreens
// - Trying to add support for custom Intents

package anywheresoftware.b4a.objects;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map.Entry;

import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.preference.RingtonePreference;
import android.text.method.PasswordTransformationMethod;
import android.widget.EditText;
import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.Author;
import anywheresoftware.b4a.BA.Hide;
import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.BA.Version;
import anywheresoftware.b4a.keywords.Common;
import anywheresoftware.b4a.objects.collections.List;
import anywheresoftware.b4a.objects.collections.Map;
@Version(1.01f)
@Author("Erel Uziel / Markus Stipp")

public class preferenceactivity extends PreferenceActivity{
   
   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      Intent in = getIntent();
      if (in == null || in.hasExtra("preferences") == false) {
         Common.Log("Intent missing root node.");
         return;
      }
      
      PreferenceScreenWrapper p = (PreferenceScreenWrapper) in.getParcelableExtra("preferences");
      Common.Log("Nach GetExtra");
      PreferenceScreen ps = (PreferenceScreen) p.createPreference(this);
      Common.Log("Nach PreferenceScreen");
      this.setPreferenceScreen(ps);
      Common.Log("Nach SetPreferenceScreen");
      setDependency(ps, p.childs);
   }
   
   private void setDependency(PreferenceScreen ps, ArrayList<B4APreference> childs) {
      for (B4APreference b : childs) {
         if (b instanceof PreferenceCategoryWrapper) {
            setDependency(ps, ((PreferenceCategoryWrapper) b).childs);
         }
         else if (b instanceof PreferenceScreenWrapper) {
            setDependency(ps, ((PreferenceScreenWrapper) b).childs);
         }
         else {
            if (b.dependency.length() > 0 && ps.findPreference(b.dependency) == null)
               Common.Log("Dependency key does not exist: " + b.dependency);
            else
               ps.findPreference(b.key).setDependency(b.dependency);
         }
      }
      
   }
   /**
    * Provides access to the saved settings. Using PreferenceManager you can get the stored values and modify them.
    */
   @ShortName("AHPreferenceManager")
   public static class PreferenceManager {
      private SharedPreferences sp = android.preference.PreferenceManager.getDefaultSharedPreferences(BA.applicationContext);
      private HashSet<String> updatedKeys = new HashSet<String>();
      public PreferenceManager() {
         sp.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() {

            @Override
            public void onSharedPreferenceChanged(
                  SharedPreferences sharedPreferences, String key) {
               updatedKeys.add(key);
            }
            
         });
      }
      /**
       * PreferenceActivity library allows you to show the standard settings interface and provides an easy way to handle applications settings.
       *This library requires you to modify AndroidManifest.xml. See the <link>tutorial|http://www.b4x.com/forum/basic4android-getting-started-tutorials/10608-preferenceactivity-tutorial.html</link> for more information.
       */
      public static void LIBRARY_DOC() {
         
      }
      /**
       * Returns a list with the keys that were updated since the last call to GetUpdatedKeys.
       *Note that the updated keys may include keys with unchanged values.
       */
      public List GetUpdatedKeys() {
         List l = new List();
         l.Initialize();
         for (String s : updatedKeys) {
            l.Add(s);
         }
         updatedKeys.clear();
         return l;
      }
      /**
       * Returns the String value mapped to the given key. Returns an empty string if the key is not found.
       */
      public String GetString(String Key) {
         return sp.getString(Key, "");
      }
      /**
       * Returns the Boolean value mapped to the given key. Returns False is the key is not found.
       */
      public boolean GetBoolean(String Key) {
         return sp.getBoolean(Key, false);
      }
      /**
       * Returns a Map with all the Keys and Values. Note that changes to this map will not affect the stored values.
       */
      public Map GetAll() {
         Map m = new Map();
         m.Initialize();
         for (Entry<String, ?> e : sp.getAll().entrySet()) {
            m.Put(e.getKey(), e.getValue());
         }
         return m;
      }
      /**
       * Maps the given key to the given String value.
       */
      public void SetString(String Key, String Value) {
         Editor e = sp.edit();
         e.putString(Key, Value);
         e.commit();
      }
      /**
       * Maps the given key to the given Boolean value.
       */
      public void SetBoolean(String Key, boolean Value) {
         Editor e = sp.edit();
         e.putBoolean(Key, Value);
         e.commit();
      }
      /**
       * Clears all stored entries.
       */
      public void ClearAll() {
         Editor e = sp.edit();
         e.clear();
         e.commit();
      }
   }
   /**
    * 
    */
   @ShortName("AHPreferenceScreen")
   public static class PreferenceScreenWrapper extends B4APreference {
      
      protected ArrayList<B4APreference> childs;
      protected static int randomKey;
      public PreferenceScreenWrapper() {
         this("PreferenceScreen" + Integer.toString(++randomKey));
      }
      protected PreferenceScreenWrapper(String key) {
         super(key, null, null, null, null);
      }
      public PreferenceScreenWrapper(Parcel source) {
         super(source);

         source.readTypedList(childs, B4APreference.CREATOR);
         randomKey = source.readInt();
      }
      /**
       * Initializes the object and sets the title that will show. The summary will show for secondary PreferenceScreens.
       */
      public void Initialize(String Title, String Summary) {
         childs = new ArrayList<B4APreference>();
         this.title = Title;
         this.summary = Summary;
      }
      /**
       * Creates the Intent object that is required for showing the PreferencesActivity.
       *Example:<code>StartActivity(PreferenceScreen1.CreateIntent)</code>
       */
      public Intent CreateIntent() {
         Intent in = new Intent();
         in.setClass(BA.applicationContext, preferenceactivity.class);
         in.putExtra("preferences", this);
         return in;
      }
      @Override
      Preference createPreference(PreferenceActivity c) {
         PreferenceScreen ps = c.getPreferenceManager().createPreferenceScreen(c);
         handleDefaults(ps);
         for (B4APreference b : childs) {
            if (b instanceof PreferenceCategoryWrapper) {
               ((PreferenceCategoryWrapper)b).parent = ps;
               b.createPreference(c);
            }
            else {
               ps.addPreference(b.createPreference(c));
            }
         }
         return ps;
      }
      /**
       * Adds a preference entry with a check box. The entry values can be either True or False.
       *Key - The preference key associated with the value.
       *Title - Entry title.
       *SummaryOn - Entry summary (second row).
       *SummaryOff - Entry summary if checkbox is not checked (Set to empty string to always show SummaryOn text)
       *DefaultValue - The default value of this preference entry if the key does not already exist.
       *Dependency - A key of a preference this preference entry depends on.
       */
      public void AddCheckBox(String Key, String Title, final String SummaryOn, final String SummaryOff, boolean DefaultValue, String Dependency) {
         childs.add(new B4APreference(Key, Title, SummaryOn, DefaultValue, Dependency) {
            @Override
            Preference createPreference(PreferenceActivity c) {
               CheckBoxPreference cbp = new CheckBoxPreference(c);
               handleDefaults(cbp);
               if (SummaryOff.length() > 0) {
                  cbp.setSummaryOn(SummaryOn);
                  cbp.setSummaryOff(SummaryOff);
               }
               return cbp;
            }
         });
      }
      /**
       * Adds a preference entry which allows the user to choose a single item out of a list.
       *Key - The preference key associated with the value.
       *Title - Entry title.
       *Summary - Entry summary (second row).
       *DefaultValue - The default value of this preference entry if the key does not already exist. Should match one of the values.
       *Dependency - A key of a preference this preference entry depends on.
       *Values - A list of strings with the possible values.
       */
      public void AddList(String Key, String Title, String Summary, String DefaultValue, String Dependency, List Values) {
         final CharSequence[] values = new String[Values.getSize()];
         for (int i = 0;i < values.length;i++) {
            values[i] = String.valueOf(Values.Get(i));
         }
         childs.add(new B4APreference(Key, Title, Summary, DefaultValue, Dependency) {
            @Override
            Preference createPreference(PreferenceActivity c) {
               ListPreference list = new ListPreference(c);
               handleDefaults(list);
               list.setDialogTitle(title);
               
               list.setEntries(values);
               list.setEntryValues(values);
               return list;
            }
         });
      }
      /**
       * Adds a preference entry which allows the user to choose a single item out of a list.
       *Key - The preference key associated with the value.
       *Title - Entry title.
       *Summary - Entry summary (second row).
       *DefaultValue - The default value of this preference entry if the key does not already exist. Should match one of the value keys.
       *Dependency - A key of a preference this preference entry depends on.
       *Values - A map with the possible values. The key of the map is used as the value for the preference and the value is displayed in the list.
       */
      public void AddList2(String Key, String Title, String Summary, String DefaultValue, String Dependency, Map Values) {
         final CharSequence[] values = new String[Values.getSize()];
         final CharSequence[] keys = new String[Values.getSize()];
         for (int i = 0;i < values.length;i++) {
            values[i] = String.valueOf(Values.GetValueAt(i));
            keys[i] = String.valueOf(Values.GetKeyAt(i));
         }
         childs.add(new B4APreference(Key, Title, Summary, DefaultValue, Dependency) {
            @Override
            Preference createPreference(PreferenceActivity c) {
               ListPreference list = new ListPreference(c);
               handleDefaults(list);
               list.setDialogTitle(title);
               
               list.setEntries(values);
               list.setEntryValues(keys);
               return list;
            }
         });
      }
      
      public int RT_RINGTONE = 1;
      public int RT_NOTIFICATION = 2;
      public int RT_ALARM = 4;
      
      /**
       * Adds a preference entry which allows the user to choose a ring tone out of a list.
       *Key - The preference key associated with the value.
       *Title - Entry title.
       *Summary - Entry summary (second row).
       *DefaultValue - The default value of this preference entry if the key does not already exist. Should match one of the value keys.
       *Dependency - A key of a preference this preference entry depends on.
       *RingToneType - Type of the Ringtone. Use RT_RINGTONE, RT_NOTIFICATION, RT_ALARM.
       */
      public void AddRingtone(String Key, String Title, String Summary, String DefaultValue, String Dependency, final int RingToneType) {
         childs.add(new B4APreference(Key, Title, Summary, DefaultValue, Dependency) {
            @Override
            Preference createPreference(PreferenceActivity c) {
               RingtonePreference ring = new RingtonePreference(c);
               handleDefaults(ring);
               ring.setRingtoneType(RingToneType);               
               return ring;
            }
         });
      }
      /**
       * Adds a preference entry which allows the user to enter free text.
       *Key - The preference key associated with the value.
       *Title - Entry title.
       *Summary - Entry summary (second row).
       *DefaultValue - The default value of this preference entry if the key does not already exist.
       *Dependency - A key of a preference this preference entry depends on.
       */
      public void AddEditText(String Key, final String Title, final String Summary, final String DefaultValue, final String Dependency) {
         childs.add(new B4APreference(Key, Title, Summary, DefaultValue, Dependency) {
            @Override
            Preference createPreference(PreferenceActivity c) {
               EditTextPreference e = new EditTextPreference(c);
               e.setDialogTitle(title);
               handleDefaults(e);
               return e;
            }
         });
      }
      /**
       * Adds a preference entry which allows the user to enter a password. Be aware that the password is saved uncrypted in the preferences file!
       *Key - The preference key associated with the value.
       *Title - Entry title.
       *Summary - Entry summary (second row).
       *DefaultValue - The default value of this preference entry if the key does not already exist.
       *Dependency - A key of a preference this preference entry depends on.
       */
      public void AddPassword(String Key, final String Title, final String Summary, final String DefaultValue, final String Dependency) {
         childs.add(new B4APreference(Key, Title, Summary, DefaultValue, Dependency) {
            @Override
            Preference createPreference(PreferenceActivity c) {
               EditTextPreference e = new EditTextPreference(c);
               e.setDialogTitle(title);
               EditText et = e.getEditText();
               et.setTransformationMethod(new PasswordTransformationMethod());
               handleDefaults(e);
               return e;
            }
         });
      }
      /**
       * Adds a secondary PreferenceScreen. When the user presses on this entry the second screen will appear.
       */
      public void AddPreferenceScreen(PreferenceScreenWrapper PreferenceScreen) {
         childs.add(PreferenceScreen);
      }
      /**
       * Calls the given Intent
       */
      public void AddIntent(String Title, String Summary, final Intent Intent) {
         childs.add(new B4APreference(Title, Summary, Intent) {
            @Override
            Preference createPreference(PreferenceActivity c) {
               PreferenceScreen ps = c.getPreferenceManager().createPreferenceScreen(c);
               handleDefaults(ps);
               return ps;
            }
         });
      }
      
      /**
       * Adds a PreferenceCategory. A preference category is made of a title and a group of entries.
       *Note that a PreferenceCategory cannot hold other PreferenceCategories.
       */
      public void AddPreferenceCategory(PreferenceCategoryWrapper PreferenceCategory) {
         childs.add(PreferenceCategory);
      }   

      @Override
      public void writeToParcel(Parcel dest, int flags) {
         super.writeToParcel(dest, flags);

         dest.writeTypedList(childs);
         dest.writeInt(randomKey);
      }   
      
      
      public static final Parcelable.Creator CREATOR =
         new Parcelable.Creator() {
            public PreferenceScreenWrapper createFromParcel(Parcel source) {
               return new PreferenceScreenWrapper(source);
            }

            @Override
            public B4APreference[] newArray(int size) {
               return new B4APreference[size];
            }
         };
   
   
   }
   /**
    * PreferenceCategory holds a group of other preferences.
    */
   @ShortName("AHPreferenceCategory")
   public static class PreferenceCategoryWrapper extends PreferenceScreenWrapper {
      PreferenceGroup parent;
      public PreferenceCategoryWrapper() {
         super("PreferenceCategory" + Integer.toString(++randomKey));
      }
      /**
       * Initializes the object and sets the category title.
       */
      public void Initialize(String Title) {
         childs = new ArrayList<B4APreference>();
         this.title = Title;
      }
      @Override
      Preference createPreference(PreferenceActivity c) {
         PreferenceCategory pc = new PreferenceCategory(c);
         handleDefaults(pc);
         if (parent == null) {
            Common.Log("Error: PreferenceCategories cannot be nested!");
         }
         parent.addPreference(pc);
         for (B4APreference b : childs) {
            pc.addPreference(b.createPreference(c));
         }
         return pc;
      }
      @Override
      @Hide
      public Intent CreateIntent() {
         return null;
      }
   }
   static abstract class B4APreference implements Parcelable{
      String key;
      Serializable defaultValue;
      String dependency;
      String title, summary;
      Intent intent;
      public B4APreference(String key, String title, String summary, Serializable defaultValue, String dependency) {
         this.key = key;
         this.defaultValue = defaultValue;
         this.summary = summary;
         this.title = title;
         this.dependency = dependency;
      }
      public B4APreference(String title, String summary, Intent intent) {
         this.summary = summary;
         this.title = title;
         this.intent = intent;
      }
      
      abstract Preference createPreference(PreferenceActivity c) ;
      protected void handleDefaults(Preference p) {
         p.setKey(key);
         p.setDefaultValue(defaultValue);
         p.setTitle(title);
         p.setSummary(summary);
      }

      @Override
      public int describeContents() {
         return this.hashCode();
      }

      public B4APreference(Parcel source) {
         key = source.readString();
         defaultValue = source.readSerializable();
         dependency = source.readString();
         title = source.readString();
         summary = source.readString();
         intent = source.readParcelable(Intent.class.getClassLoader());
      }

      @Override
      public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(key);
         dest.writeSerializable(defaultValue);
         dest.writeString(dependency);
         dest.writeString(title);
         dest.writeString(summary);
         dest.writeParcelable(intent, flags);
      }   
      
      public static final Parcelable.Creator CREATOR =
         new Parcelable.Creator() {
            public B4APreference createFromParcel(Parcel source) {
               return new B4APreference(source) {
                  
                  @Override
                  public Preference createPreference(PreferenceActivity c) {
                     // TODO Auto-generated method stub
                     return null;
                  }
               };
            }

            @Override
            public B4APreference[] newArray(int size) {
               return new B4APreference[size];
            }
         };      
   }
}
 

corwin42

Expert
Licensed User
Longtime User
Yes, some users asked for possibility to call GPS Settings page for example from their own settings page.

So my idea was to just add a method AddIntent which uses a PreferenceScreen object and sets the Intent property of it. This should call the intent when the item is pressed.

I will try your idea with the activity name.
 

corwin42

Expert
Licensed User
Longtime User
Thanks very much.

It works. I currently just store the action from the Intent in the class and create a new Intent from it later.

This took me 3 hours yesterday night and after your hint it works after 5 minutes. :BangHead:

I still have to learn much about Java and Android...
:sign0104:
 
Top