MultiSelectListPreference onPreferenceChange() method getting wrong parameters - android

I have an Android app with a MultiSelectListPreference, and I'm using the onPreferenceChange() method to update the Preference's summary. I've managed to write the code that updates the summary based on the newValues parameter, but the contents of the Object do not match the actual options selected by the user.
Here is my code:
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference instanceof MultiSelectListPreference) {
List<String> newValues = new ArrayList<>((HashSet<String>) newValue);
MultiSelectListPreference pref = (MultiSelectListPreference) preference;
ArrayList<String> newSummary = new ArrayList<>();
ArrayList<CharSequence> values = new ArrayList<>(Arrays.asList(pref.getEntryValues()));
for (int i = 0; i < newValues.size(); i++) {
int currentIndex = findIndexOfString(values, newValues.get(i).replaceAll(" ", ""));
String title = (currentIndex >= 0) ? pref.getEntries()[currentIndex].toString().replaceAll(" ", "") : "";
newSummary.add(title);
}
pref.setSummary(TextUtils.join(", ", newSummary));
}
return true;
}
private static int findIndexOfString(List<CharSequence> list, String s) {
for (int i = 0; i < list.size(); i++) {
if (s.equals(list.get(i).toString().replaceAll(" ", ""))) {
return i;
}
}
return -1;
}

This is the code I'm using to set summary based on the newValue Object received from onPreferenceChange(), which contains the values stored as a preference. (Not good for the summary)
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference instanceof MultiSelectListPreference) {
List<String> newValues = new ArrayList<>((HashSet<String>) newValue);
pref.setSummary(TextUtils.join(", ", getSummaryListFromValueList(newValues)));
}
return true;
}
private List<String> getSummaryListFromValueList(List<String> valueList) {
String[] allSummaries = getResources().getStringArray(R.array.pref_notif);
String[] allValues = getResources().getStringArray(R.array.pref_notif_values);
List<String> summaryList = new ArrayList<>();
for (int i = 0; i < allValues.length; i++) {
for (String value : valueList) {
if (allValues[i].equals(value)) {
summaryList.add(allSummaries[i]);
}
}
}
return summaryList;
}

Related

Showing selected values in summary from multiselectlistpreference

I Have a MultiSelectListPreference which shows a the list of weeks when user close the multi select dialog i would like to show the selected values in summary using %s but it only works with ListPreference. Is there any solution for this?
<MultiSelectListPreference
android:id="#+id/pref_select_week"
android:defaultValue="#array/week_array_values"
android:entries="#array/week_array"
android:entryValues="#array/week_array_values"
android:icon="#drawable/ic_pref_time"
android:key="keyZodiacSign"
android:summary="%s"
android:title="Select Days" />
First add an extension method to MultiSelectListPreference which sets the summary from the selected values:
fun MultiSelectListPreference.setSummaryFromValues(values: Set<String>) {
summary = values.map {entries[findIndexOfValue(it)]}.joinToString(", ")
}
And then in eg. onCreateView of your PreferenceFragmentCompat:
val columnSelect = findPreference("EXPORT_COLUMNS") as MultiSelectListPreference
columnSelect.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
columnSelect.setSummaryFromValues(newValue as Set<String>)
true
}
columnSelect.setSummaryFromValues(columnSelect.values)
You need to create custom version of MultiSelectListPreference and ovveride getSummary method like this:
#Override
public CharSequence getSummary() {
CharSequence cs = super.getSummary();
String summary = cs.toString();
if (summary.contains("%s")) {
String text = "";
StringBuilder builder = new StringBuilder();
CharSequence[] entries = getEntries();
if(entries.length > 0) {
CharSequence[] entryValues = getEntryValues();
Set<String> values = getValues();
int pos = 0;
for (String value : values) {
pos++;
int index = -1;
for (int i = 0; i < entryValues.length; i++) {
if (entryValues[i].equals(value)) {
index = i;
break;
}
}
builder.append(entries[index]);
if (pos < values.size())
builder.append(", ");
}
text = builder.toString();
}
summary = String.format(summary, text);
}
return summary;
}
In Kotlin at least, you could add a function to the MultiSelectListPreference class as follows:
import android.support.v14.preference.MultiSelectListPreference
fun MultiSelectListPreference.useSelectionAsSummary() {
var summary = ""
for (value in this.values) {
if (summary.isNotEmpty()) {
summary += ", "
}
val i = this.findIndexOfValue(value.toString())
summary += this.entries[i]
}
this.summary = summary
}
Usage as follows:
prefSelectWeek.useSelectionAsSummary()

Sorting with Comparator not working

I have to store and manage a volume list in the format:
"100 ml", "200 ml", "300 ml"...
I'm using the SharedPreferences with JSON to store this everytime the list is changed.
I want the list to be ordered, so 100 < 1000, but it is showing like 1000 and 2000 before 300.
Here is my comparator:
mVolumeComparator = new Comparator<String>() {
#Override
public int compare(String s1, String s2) {
int volume1 = Integer.parseInt(s1.replace(" ml", ""));
int volume2 = Integer.parseInt(s2.replace(" ml", ""));
if (volume1 > volume2) {
return 1;
} else if (volume2 > volume1) {
return -1;
} else {
return 0;
}
}
};
And here is my get method:
public static ArrayList<String> getVolumesFromPreference(Context ctx) {
if (!ctx.getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE).contains(KEY_VOLUMES_BUNDLE)) {
startDefaultVolumes(ctx, KEY_VOLUMES_BUNDLE);
}
try {
JSONArray jsonArray = new JSONArray(ctx.getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE).getString(KEY_VOLUMES_BUNDLE, null));
ArrayList<String> lista = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
lista.add(jsonArray.getString(i));
}
Collections.sort(lista, mVolumeComparator);
return lista;
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
private static void startDefaultVolumes(Context ctx, String key_bundle) {
JSONArray jsonArray = new JSONArray();
jsonArray.put("269 ml");
jsonArray.put("350 ml");
jsonArray.put("473 ml");
jsonArray.put("550 ml");
jsonArray.put("600 ml");
SharedPreferences.Editor editor = ctx.getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE).edit();
editor.putString(key_bundle, jsonArray.toString());
editor.commit();
}
use Integer.valueOf() method instead Integer.parceInt()
I will recommend you to store only the value "100", "1000", "300" and UNIT if you have more than unit available.
SO, you can order it just with a normal Integer comparator, and then apply the "ml" suffix at runtime
Try this code for sorting arraylist in ascending order.
ArrayList<int> lista = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
lista.add(Integer.parseInt(jsonArray.getString(i)));
}
Collections.sort(lista , new Comparator<Integer >() {
#Override
public int compare(Integer lhs, Integer rhs) {
return Integer.valueOf(lhs).compareTo(Integer.valueOf(rhs));
}
});

receive a string message from user input in android and parse to an int so can be stored in an array and compared with another array of integers

I am having a problem with the above task in my android application. I am accepting user input from the EditText widget in the form of String. I accepting numbers from the user so I have to parse them to integers so they can be compared with another array of integers. I have the line:
String message = editText.getText().toString()
then to try and parse the String to an int I have the code line:
int userNumbers = Integer.parseInt(message).
However when I attempt to compare the array userArray with the array numbers I am getting the error that "Incompatible operand types String and Integer.
Can anyone see where my problem is or how I can solve it? Here's my code:
Thanks in advance.
public class MainActivity extends Activity {
public final static String EXTRA_MESSAGE = ".com.example.lotterychecker.MESSAGE";
static boolean bonus = false;
static boolean jackpot = false;
static int lottCount = 0;
Button check;
Integer [] numbers;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//link to the intended web site and get the lottery numbers while the app is opening
try {
Document doc = Jsoup.connect("http://www.national-lottery.co.uk/player/p/drawHistory.do").userAgent("Mozilla").get();
Elements elements = doc.getElementsByClass("drawhistory");
Element table = elements.first();
Element tbody = table.getElementsByTag("tbody").first();
Element firstLottoRow = tbody.getElementsByClass("lottorow").first();
Element dateElement = firstLottoRow.child(0);
System.out.println(dateElement.text());
Element gameElement = firstLottoRow.child(1);
System.out.println(gameElement.text());
Element noElement = firstLottoRow.child(2);
System.out.println(noElement.text());
String [] split = noElement.text().split(" - ");
// set up an array to store numbers from the latest draw on the lottery web page
Integer [] numbers = new Integer [split.length];
int i = 0;
for (String strNo : split) {
numbers [i] = Integer.valueOf(strNo);
i++;
}
for (Integer no : numbers) {
System.out.println(no);
}
Element bonusElement = firstLottoRow.child(3);
Integer bonusBall = Integer.valueOf(bonusElement.text());
System.out.println("Bonus ball: " + bonusBall);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
//called when the user clicks the send button
public void checkNumbers(View view) {
final int SIZE =6;
String [] userArray = new String[SIZE];
//create an intent to display the numbers
Intent intent = new Intent(this, DisplayNumbersActivity.class);
EditText editText = (EditText) findViewById(R.id.enter_numbers);
String message = editText.getText().toString();
intent.putExtra(EXTRA_MESSAGE, message );
startActivity(intent);
//parse string message to an int for user numbers
try{
int userNumbers = Integer.parseInt(message); //is this right?
}//try
catch (NumberFormatException e)
{
System.out.println("Not a number" + e.getMessage());
}
Toast.makeText(MainActivity.this, "Here are your numbers", Toast.LENGTH_LONG).show();
for (int count =0; count < SIZE; count ++)
{
if (check.isPressed())
{
userArray[count] = editText.getText().toString();
}
}//for
//compare the two arrays of integers
for (int loop = 0; loop < userArray.length; loop++)
{
for (int loopOther = 0; loopOther < numbers.length; loopOther++)
{
if (userArray[loop] == numbers[loopOther]) //how do I parse this?
{
lottCount++;
}else if (userArray[loop] == bonus)
{
bonus = true;
}
}//for
}//for main
You have this
Integer [] numbers; // numbers is an integer array
You have string array
String [] userArray = new String[SIZE]; // userArray is a string array
You compare like below
if (userArray[loop] == numbers[loopOther])
So you get the error Incompatible operand types String and Integer.
try
if (Integer.parseInt(userArray[loop]) == numbers[loopOther])
Enclosing the above with try catch block
String message = editText.getText().toString();
try{
int userNumbers = Integer.parseInt(message);
//is this right? yes
}
catch (NumberFormatException e)
{
e.printStacktrace();
}
Change String to Int here:
for (int loop = 0; loop < userArray.length; loop++)
{
for (int loopOther = 0; loopOther < numbers.length; loopOther++)
{
if (Integer.valueOf(userArray[loop]) == numbers[loopOther]) //how do I parse this?
{
lottCount++;
}else if (Integer.valueOf(userArray[loop]) == bonus)
{
bonus = true;
}
}//for
}//for main
Parse Like this :
for (int loop = 0; loop < userArray.length; loop++)
{
for (int loopOther = 0; loopOther < numbers.length; loopOther++)
{
if (Integer.parseInt(userArray[loop]) == numbers[loopOther])
{
lottCount++;
}else if (userArray[loop] == bonus)
{
bonus = true;
}
}
}

Custom ListPreference to choose apps with Images

I would like to make a custom ListPreference to let the user choose from his apps. This currently works great. Except:
Filter out system apps (I've filtered by (info.activityInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 1 but it still displays system apps)
Icons (Except "check all" item; I guess I need to manipulate the Layout somehow?; Found the solution: http://www.devmil.de/?p=63)
preferences.xml
<com.example.gui.preference.ApplicationListPreference
android:key="pref_key_choose_apps"
android:dependency="pref_key_enable_server"
android:title="#string/choose_apps"
android:dialogTitle="#string/choose_apps"
android:entries="#array/pref_entries_choose_apps"
android:entryValues="#array/pref_values_choose_apps"
example:checkAll="#ALL#" />
arrays.xml
<string-array name="pref_values_choose_apps">
<item>#ALL#</item>
</string-array>
<string-array name="pref_entries_choose_apps">
<item>All</item>
</string-array>
attr.xml
<declare-styleable name="ApplicationListPreference">
<attr format="string" name="checkAll" />
</declare-styleable>
ApplicationListPreference:
public class ApplicationListPreference extends ListPreference {
private final static String SEPARATOR = ";";
private String checkAllKey = null;
private boolean[] mClickedDialogEntryIndices;
public ApplicationListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
checkAllKey = context.obtainStyledAttributes(attrs, R.styleable.ApplicationListPreference).getString(R.styleable.ApplicationListPreference_checkAll);
List<CharSequence> entries = new ArrayList<CharSequence>();
for (CharSequence entry : getEntries()) {
entries.add(entry);
}
List<CharSequence> entryValues = new ArrayList<CharSequence>();
for (CharSequence entryValue : getEntryValues()) {
entryValues.add(entryValue);
}
Intent intentFilter = new Intent(Intent.ACTION_MAIN, null);
intentFilter.addCategory(Intent.CATEGORY_LAUNCHER);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> appList = pm.queryIntentActivities(intentFilter, PackageManager.PERMISSION_GRANTED);
Collections.sort(appList, new ResolveInfo.DisplayNameComparator(pm));
for (ResolveInfo info : appList) {
if ((info.activityInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 1) {
entryValues.add(info.activityInfo.packageName);
entries.add(info.loadLabel(pm).toString());
}
}
setEntries(entries.toArray(new CharSequence[entries.size()]));
setEntryValues(entryValues.toArray(new CharSequence[entryValues.size()]));
}
public ApplicationListPreference(Context context) {
this(context, null);
}
#Override
public void setEntries(CharSequence[] entries) {
super.setEntries(entries);
mClickedDialogEntryIndices = new boolean[entries.length];
}
#Override
protected void onPrepareDialogBuilder(Builder builder) {
CharSequence[] entries = getEntries();
CharSequence[] entryValues = getEntryValues();
if (entries == null || entryValues == null || entries.length != entryValues.length) {
throw new IllegalStateException("Irregular array length");
}
restoreCheckedEntries();
builder.setMultiChoiceItems(entries, mClickedDialogEntryIndices, new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog, int which, boolean val) {
if (isCheckAllValue(which) == true) {
checkAll(dialog, val);
}
mClickedDialogEntryIndices[which] = val;
}
});
}
private boolean isCheckAllValue(int which) {
final CharSequence[] entryValues = getEntryValues();
if (checkAllKey != null) {
return entryValues[which].equals(checkAllKey);
}
return false;
}
private void checkAll(DialogInterface dialog, boolean val) {
ListView lv = ((AlertDialog) dialog).getListView();
int size = lv.getCount();
for (int i = 0; i < size; i++) {
lv.setItemChecked(i, val);
mClickedDialogEntryIndices[i] = val;
}
}
public String[] parseStoredValue(CharSequence val) {
if (val == null || "".equals(val)) {
return null;
}
return ((String) val).split(SEPARATOR);
}
private void restoreCheckedEntries() {
CharSequence[] entryValues = getEntryValues();
String[] vals = parseStoredValue(getValue());
if (vals != null) {
List<String> valuesList = Arrays.asList(vals);
for (int i = 0; i < entryValues.length; i++) {
CharSequence entry = entryValues[i];
if (valuesList.contains(entry)) {
mClickedDialogEntryIndices[i] = true;
}
}
}
}
#Override
protected void onDialogClosed(boolean positiveResult) {
ArrayList<String> values = new ArrayList<String>();
CharSequence[] entryValues = getEntryValues();
if (positiveResult && entryValues != null) {
for (int i = 0; i < entryValues.length; i++) {
if (mClickedDialogEntryIndices[i] == true) {
String val = (String) entryValues[i];
if (checkAllKey == null || (val.equals(checkAllKey) == false)) {
values.add(val);
}
}
}
String value = join(values);
if (callChangeListener(value)) {
setValue(value);
}
}
}
protected static String join(Iterable<? extends Object> pColl) {
Iterator< ? extends Object > oIter;
if (pColl == null || (!(oIter = pColl.iterator()).hasNext())) {
return "";
}
StringBuilder oBuilder = new StringBuilder(String.valueOf(oIter.next()));
while (oIter.hasNext()) {
oBuilder.append(SEPARATOR).append(oIter.next());
}
return oBuilder.toString();
}
public static boolean contains(String straw, String haystack) {
for (String val : haystack.split(SEPARATOR)) {
if (val.equals(straw)) {
return true;
}
}
return false;
}
}
For the system apps filtering, you should be checking the flags in the ApplicationInfo instead of ActivityInfo. Try modifying this:
(info.activityInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 1
to
(info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 1

How can I store an integer array in SharedPreferences?

I want to save/recall an integer array using SharedPreferences. Is this possible?
You can try to do it this way:
Put your integers into a string, delimiting every int by a character, for example a comma, and then save them as a string:
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
int[] list = new int[10];
StringBuilder str = new StringBuilder();
for (int i = 0; i < list.length; i++) {
str.append(list[i]).append(",");
}
prefs.edit().putString("string", str.toString());
Get the string and parse it using StringTokenizer:
String savedString = prefs.getString("string", "");
StringTokenizer st = new StringTokenizer(savedString, ",");
int[] savedList = new int[10];
for (int i = 0; i < 10; i++) {
savedList[i] = Integer.parseInt(st.nextToken());
}
You can't put Arrays in SharedPreferences, but you can workaround:
private static final String LEN_PREFIX = "Count_";
private static final String VAL_PREFIX = "IntValue_";
public void storeIntArray(String name, int[] array){
SharedPreferences.Editor edit= mContext.getSharedPreferences("NAME", Context.MODE_PRIVATE).edit();
edit.putInt(LEN_PREFIX + name, array.length);
int count = 0;
for (int i: array){
edit.putInt(VAL_PREFIX + name + count++, i);
}
edit.commit();
}
public int[] getFromPrefs(String name){
int[] ret;
SharedPreferences prefs = mContext.getSharedPreferences("NAME", Context.MODE_PRIVATE);
int count = prefs.getInt(LEN_PREFIX + name, 0);
ret = new int[count];
for (int i = 0; i < count; i++){
ret[i] = prefs.getInt(VAL_PREFIX+ name + i, i);
}
return ret;
}
Here's my version, based on Egor's answer. I prefer not to use StringBuilder unless I'm building an enourmous string, but thanks to Egor for using StringTokenizer -- haven't made much use of this in the past, but it's very handy! FYI, this went in my Utility class:
public static void saveIntListPrefs(
String name, Activity activity, List<Integer> list)
{
String s = "";
for (Integer i : list) {
s += i + ",";
}
Editor editor = activity.getPreferences(Context.MODE_PRIVATE).edit();
editor.putString(name, s);
editor.commit();
}
public static ArrayList<Integer> readIntArrayPrefs(String name, Activity activity)
{
SharedPreferences prefs = activity.getPreferences(Context.MODE_PRIVATE);
String s = prefs.getString(name, "");
StringTokenizer st = new StringTokenizer(s, ",");
ArrayList<Integer> result = new ArrayList<Integer>();
while (st.hasMoreTokens()) {
result.add(Integer.parseInt(st.nextToken()));
}
return result;
}
I like to use JSON, which can be stored and retrieved as a string, to represent any complex data in SharedPreferences.
So, in the case of an int array:
public void setPrefIntArray(String tag, int[] value)
{
SharedPreferences.Editor prefEditor = PreferenceManager.getDefaultSharedPreferences(context)
.edit();
String s;
try
{
JSONArray jsonArr = new JSONArray();
for (int i : value)
jsonArr.put(i);
JSONObject json = new JSONObject();
json.put(tag, jsonArr);
s = json.toString();
}
catch(JSONException excp)
{
s = "";
}
prefEditor.putString(tag, s);
prefEditor.commit();
}
public int[] getPrefIntArray(String tag, int[] defaultValue)
{
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
String s = pref.getString(tag, "");
try
{
JSONObject json = new JSONObject(new JSONTokener(s));
JSONArray jsonArr = json.getJSONArray(tag);
int[] result = new int[jsonArr.length()];
for (int i = 0; i < jsonArr.length(); i++)
result[i] = jsonArr.getInt(i);
return result;
}
catch(JSONException excp)
{
return defaultValue;
}
}
The beauty is that the same idea can be applied to any other complex data representable as a JSON.
Two solutions:
(1) Use http://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html
It has split/join functions that let you join and split the integers in one liners:
StringUtils.join([1, 2, 3], ';') = "1;2;3"
StringUtils.split("1;2;3", ';') = ["1", "2", "3"]
You'd still have to convert the strings back to integers, though.
Actually, for splitting java.lang.String.split() will work just as fine:
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#split(java.lang.String)
(2) Use the SharedPreferences.putStringSet() (API 11):
SharedPreferences.Editor editor = preferences.edit();
int count = this.intSet.size();
if (count > 0) {
Set<String> theSet = new HashSet<String>();
for (Long l : this.intSet) {
theSet.add(String.valueOf(l));
}
editor.putStringSet(PREFS_KEY, theSet);
} else {
editor.remove(PREFS_KEY);
}
editor.commit();
And to get it back:
Set<String> theSet = this.preferences.getStringSet(PREFS_KEY, null);
if (theSet != null && !theSet.isEmpty()) {
this.intSet.clear();
for (String s : theSet) {
this.intSet.add(Integer.valueOf(s));
}
}
This code does not catch the NPEs or NumberFormatExceptions because the intSet is otherwise assured to not contain any nulls. But of course, if you cannot assure that in your code you should surround this with a try/catch.
Here is how the "convert to comma-separated String" solution could look in Kotlin, implemented as extension functions:
fun SharedPreferences.Editor.putIntArray(key: String, value: IntArray): SharedPreferences.Editor {
return putString(key, value.joinToString(
separator = ",",
transform = { it.toString() }))
}
fun SharedPreferences.getIntArray(key: String): IntArray {
with(getString(key, "")) {
with(if(isNotEmpty()) split(',') else return intArrayOf()) {
return IntArray(count(), { this[it].toInt() })
}
}
}
That way you can use putIntArray(String, IntArray) and getIntArray(String) just like the other put and set methods:
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
prefs.edit().putIntArray(INT_ARRAY_TEST_KEY, intArrayOf(1, 2, 3)).apply()
val intArray = prefs.getIntArray(INT_ARRAY_TEST_KEY)
I went for the below solution, it's the least verbose of what I could see in this thread (in my case I wanted to have a set as a collection). "value" is the of type Set<Int>.
Save:
sharedPreferences.edit {
if (value.isNotEmpty()) {
putStringSet(key, hashSetOf(*value.map { it.toString() }.toTypedArray()))
} else {
remove(key)
}
}
Retrieve:
val stringSet = sharedPreferences.getStringSet(key, null)
if (stringSet.isNullOrEmpty()) return emptySet()
return setOf<Int>(*stringSet.map { Integer.valueOf(it) }.toTypedArray())
You can only save primitive values in sharedPreference. Use Sqlite instead.

Categories

Resources