SharedPreferences - CastException: HashSet cannot be cast to TreeSet - android

I am trying to store TreeSet in the SharedPreferences using the following code:
Set<String> chemicalValuesSet = new TreeSet<>();
chemicalValuesSet.add("id: " + checkForNull(jsonChemicalValues.getString("id")));
editor.putStringSet(SP_CHEMICAL_VALUES, chemicalValuesSet);
editor.apply();
However, when I try to access that TreeSet I am getting casting error, as if this set is declared as a HashSet.
SharedPreferences sharedPreferences =
getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE);
TreeSet<String> chemicalValues =
(TreeSet<String>) sharedPreferences.getStringSet(SP_CHEMICAL_VALUES, null);
I have no clue to solving this issue. In addition, when I started writing this part I was setting chemicalValuesSet as HashSet and retrieving without any problems, afterwards I decided to go with TreeSets. That's why I have tried cleaning and restarting the project, but still same issues persists.
However, if I simply change type to HashSet
in the part where I retrieve this set, it works without complaining.

You're simply making false assumptions on how SharedPreferences and its editor works. The API never guarantees that the Set you get when calling getStringSet() is the same, or even the same implementation, as the one stored when calling putStringSet(). All it says is that you can pass a Set, and that you can get a Set.
If the API documentation says that it returns a Set, you should not assume that it's returning a TreeSet, or a HashSet. Only that it's returning a Set. If you absolutely need a TreeSet, then create one and copy the items from the returned Set to the TreeSet.

You cannot cast HashSet to TreeSet straight away.
Instead you can do either of the following to add all the items in the HashSet into TreeSet but keep in mind, the items added to the TreeSet will get sorted automatically.
// Passing the collection HashSet to TreeSet
HashSet<String> hashSet = sharedPreferences.getStringSet(SP_CHEMICAL_VALUES, null);
TreeSet<String> chemicalValues = new TreeSet<String>(hashSet);
or
// Adding all the values of the HashSet to TreeSet using addAll() API
// This will help to retain the values of TreeSet (if any)
TreeSet<String> chemicalValues = new TreeSet<String>();
...
HashSet<String> hashSet = sharedPreferences.getStringSet(SP_CHEMICAL_VALUES, null);
chemicalValues.addAll(hashSet);

Actually the returned set is a hash set:
TreeSet<String> chemicalValues =
(TreeSet<String>) sharedPreferences.getStringSet(SP_CHEMICAL_VALUES, null);
You can check this conclusion in source code in SharedPreferencesImpl.java
public Set<String> getStringSet(String key, Set<String> defValues) {
synchronized (this) {
awaitLoadedLocked();
Set<String> v = (Set<String>) mMap.get(key);
return v != null ? v : defValues;
}
}
And you can also get the set type like this:
chemicalValues.getClass().getSimpleName()
HashSet and TreeSet can not be interpreted to eachother.
Hope this can help you.

Related

SharedPreferences not saving after restart

My sharedPreferences does not persist after I close the app. It always leaves the default 4 that I add the first time the app runs.
static public Boolean addFavoriteItem(Integer itemId, Context c) {
SharedPreferences s = PreferenceManager.getDefaultSharedPreferences(c);
Set<String> list = new HashSet<>();
list = s.getStringSet("favItems",list);
list.add(Integer.toString(itemId));
s.edit().putStringSet("favItems",list).apply();
Log.d("listNowAdd:",list.toString());
return true;
}
static public Boolean removeFavoriteItem(Integer itemId, Context c) {
SharedPreferences s = PreferenceManager.getDefaultSharedPreferences(c);
Set<String> list = new HashSet<>();
list = s.getStringSet("favItems",list);
list.remove(Integer.toString(itemId));
s.edit().putStringSet("favItems",list).apply();
Log.d("listNowRemove:",list.toString());
return true;
}
It does work temporarily, even after the activity is restarted, but not after the app is closed and reopened, any ideas?
You cannot modify the StringSet returned by SharedPreferences as documented:
Note that you must not modify the set instance returned by this call. The consistency of the stored data is not guaranteed if you do, nor is your ability to modify the instance at all.
Essentially what happens is that you're modifying the Set owned by SharedPreferences. It "works" while the app is running since the same (modified by you) Set is kept in memory. When you're trying to save the changes, shared preferences implementation compares the values using equals() and of course the set is equal with itself, and therefore no changes are actually saved.
You can create a new Set<String> e.g. HashSet<String> and addAll() the set you retrieved from shared preferences and do your modifications on this copy.

How to get object type of preference in Android?

I can get a String from the shared preferences by using:
sharedPreferences.getString("key_name","default value");
But how can I check if key_name is actually a String?
What if it is a Boolean key value?
Is there a method we can use like:
if(sharedPreferences.isTypeOf(Boolean,"key_name")) {}
If you know you will get a boolean you can use
sharedPreferences.getBoolean("key_name",true);
Otherwise you can do (not tested, based on doc)
Map<String, ?> all = sharedPreferences.getAll();
if(all.get("key_name") instanceof String) {
//Do something
}
else if(all.get("key_name") instanceof Boolean) {
//Do something else
}
But you are suppose to know what you stored in your SharedPrefrences
What is expected is you ought to know the data type of your SharedPreference values.
All the shared prefrences that you put are inserted into a Map.
A map cannot have duplicate keys that hold different values. When you use the put operation it basically overwrites the value associated with the key if they key already exists in the map.
You can find how a Map "put" method works here - Java Map
So checking the instanceof for two(or multiple) data types as suggested by #Maloubobola, is kind of absurd since the key can only one value and that value can be of only one data type(which you should know :P).
You can do that but it doesn't make sense like #Blackbelt commented.
All the best :)
If you expect a String, you can also use a try/catch clause:
try {
String strValue = sharedPreferences.getString("key_name","default value")
actionIfString();
} catch (ClassCastException e) {
actionIfNotString();
}

ArrayList becomes shuffled after saving and accessing in preferences

Hi guys I have recently come across a really weird problem that I cannot understand. I have tried a few work-arounds, but to no avail.
Basically as the title says, I am saving an ArrayList through the preferences but whenever I access the ArrayList it comes back shuffled.
I am drawing this to a ListView and if I have the ListView looking like:
1
2
3
4
5
6
It will come back looking like
3
2
1
6
5
4
This is my code for the onPause and on onResume
ArrayList<String> todoItems = new ArrayList<String>();
#Override
protected void onPause() {
// TODO Auto-generated method stub
SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(getBaseContext());
SharedPreferences.Editor edit = prefs.edit();
edit.putStringSet("TODOITEMS", new HashSet<String>(todoItems));
edit.commit();
super.onPause();
}
#Override
protected void onResume() {
// TODO Auto-generated method stub
ArrayList<String> returnedItems = new ArrayList<String>
(PreferenceManager.getDefaultSharedPreferences
(getBaseContext()).getStringSet("TODOITEMS",
new HashSet<String>()));
todoItems.clear();
for(int i =0; i < returnedItems.size(); i++){
todoItems.add(returnedItems.get(i));
}
aa.notifyDataSetChanged();
super.onResume();
}
it's because you are using a HashSet to store the values. HashSet doesn't guarantee that the order will be maintained.
you should use a TreeSet instead
check out Hashset vs Treeset
You're not actually saving the todoItems ArrayList and loading it. You're converting the todoItems ArrayList into a HashSet, saving it, then loading it again and re-constructing an ArrayList with it.
In that conversion from ArrayList to HashSet, the orders are not maintained. Checkout this answer on the differences between both: What is the difference between Set and List?
I think that is because you are saving the array to a HashSet.
According to the documentation:
It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time.
If you want to save a sorted list, I suggest you use SQLiteDatabase.
OR
You can sort the list as you retrieve it from the Shared Preferences. This is not good if the list is very large however.
Collections.sort(returnedItems);
As the previous two answers mention, a HashSet doesn't guarantee preserving order.
One possible solution is to serialize the ArrayList into a String first, save it using putString(), and then load it using getString() and deserialize it.
Check out the second half of this answer to see how to do that: Save ArrayList to SharedPreferences

Can't Get Shared Preference Value Even Though Values Exist

Any time I try to getInt() from a SharedPreference my app crashes, yet I can iterate through the preferences as a map. For instance, see the starred lines below:
private void loadPref(){
myPrefs = PreferenceManager.getDefaultSharedPreferences(this);
int sf = DEFAULT_VALUE;
Map<String,?> keys = myPrefs.getAll();
for(Map.Entry<String,?> entry : keys.entrySet()){
if (entry.getKey().contentEquals("score_format"))
// this works: //*****
sf = Integer.parseInt(entry.getValue().toString()); //*****
}
// but this does not: //*****
// sf = myPrefs.getInt("score_format", DEFAULT_VALUE); //*****
setScoreFormat(sf);
}
Clearly, my prefs are being saved (as evidenced by this sample and working preference screens across multiple activities). I am calling super.onCreate() before trying to access getDefaultSharedPreferences.
What should I be considering to understand why this code is not working? Why would the map work but not the "getInt" method? I did notice that the app would also crash if I tried to cast the key value explicitly... I had to cast it toString first.
What am I missing?
if you don't want to parse, make sure the score you're putting into the intent with putExtra is an int type, not a string.
Looks like object assosiated with score_format key is a String but you are trying to obtain it as int which is a mistake.

Passing multiple data to an Activity?

I'm trying to pass multiple data items in one Intent:
if (strActStat == "Sedentary") {
// passactStat.putString("keySedentary", strActStat);
// passSeden.putString("keyMale", gender);
i = new Intent(CalorieTrackerTargetWeight.this, TargetWeightResults.class);
i.putExtra("keyGender", gender);
i.putExtra("keyAct", strActStat);
//i.putExtra("keyAct", strActStat);
startActivity(i);
}
Why doesn't this work? Why can't I pass multiple items in one Intent?
You can't compare strings with ==.
if (strActStat.equals("Sedentary")) { // should work
Edit:
#Hesam has written a pretty detailed answer but his solution is not really usable. Instead of using an ArrayList<String> you should stick with the putExtra(key, value). Why? Well there are some advantages over the ArrayList solution:
you are not limited to the type of the ArrayList
you are not forced to keep a static order in you list. As you can only work with index values to get a list you need to make sure that the put() was in the same order as get(). Think of the following case: You you often send 3 values, but in some cases you don't want to send the second value. When you use the ArrayList solution, you end up sending null as the second value to ensure that the third value will stay in his place. This is highly confusing coding! Instead you should just send two values and when the receiving activity tries to receive the second value, it can handle the returning null like it want... for example replace it with a default value.
Naming of the key will grant you the knowledge of always knowing what should be inside...
Your key should be declared in the receiving Activity as a constant. So you always know by looking at this constants what intent data the activity can handle. This is good programming!
Hope this helps in clarifying the intent usage a bit.
I think this is not the only problem, first, if (strActStat == "Sedentary") this is wrong. you can't compare to string in this way. Because in this way objects are comparing not the string. Correct way is if (strActStat.equalIgnoreCase("Sedentary")).
If you use Parcelable then you can pass multiple data in just 1 intent.
Also you can use ArrayList<String>.
Here is a skeleton of the code you need:
Declare List
private List<String> test;
Init List at appropriate place
test = new ArrayList<String>();
and add data as appropriate to test.
Pass to intent as follows:
Intent intent = getIntent();
intent.putStringArrayListExtra("test", (ArrayList<String>) test);
Retrieve data as follows:
ArrayList<String> test = data.getStringArrayListExtra("test");
Hope that helps.
Try this:
done.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
namevalue=name.getText().toString();
overvalue=over.getText().toString();
audiostatus=audio.getText().toString();
Intent intent=new Intent(Settings.this,home.class);
Bundle bundle = new Bundle();
bundle.putString( "namevalue",namevalue);
bundle.putString("overvalue",overvaluse);
bundle.putInt("value",variablename);
intent.putExtras(bundle);
startActivity(intent);
}
});
I faced the same problem.
My mistake was that one of the variable I was transferring was not initialized.
Like gender or strActStat in your case.

Categories

Resources