ArrayList becomes shuffled after saving and accessing in preferences - android

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

Related

SharedPreferences - CastException: HashSet cannot be cast to TreeSet

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.

Saving and restoring ListView with Custom List Adapter

I'm currently using a custom list adapter and modifying the rows of the listview at runtime (changing text, buttons etc).
I want to find a way to save/restore the changes made to the listview at runtime when I start another fragment and then come back to the fragment containing the listview.
Currently the listview is being reloaded every time and all the runtime changes are lost.
The following shows how I am setting the list adapter.
public void setAdapterToListView(ArrayList<item> list) {
ItemList = list;
ItemAdapter adapter = new MenuItemAdapter(list, getActivity(), this);
ItemListView.setAdapter(adapter);
}
I am then using a custom list adapter to make the changes (mentioned above) at runtime. Here's a snippet of how I am changing things at runtime:
if (holder.qty.getText().toString().equals("Qty")) {
relativeLayout.setBackgroundColor(row.getResources().getColor(R.color.navDrawerL ightBlue));
holder.qty.setText("1");
holder.qty.startAnimation(anim);
holder.removeItem.setBackgroundResource(R.drawable.remove_item_red);
holder.removeItem.setEnabled(true);
...
Is there a recommended way to approach this issue?
Adapter
You will want your custom adapter to implement some sort of getter for it's internal data. For instance
public ArrayList<YourDataType> getList() {
return new ArrayList<YourDataType>(mAdapterData);
}
Activity
Then in your activity/fragment, you'll need to save and restore that data.
private static final String STATE_LIST = "State Adapter Data"
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(STATE_LIST, getAdapter().getList());
}
While there is an onRestoreInstanceState() method you could override, I typically restore during onCreate(). Usually more convenient to when other things are getting instantiated. Either or is viable.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//If restoring from state, load the list from the bundle
if (savedInstanceState != null) {
ArrayList<YourDataType> list = savedInstanceState.getParcelableArrayList(STATE_LIST);
ItemAdapter adapter = new MenuItemAdapter(list, getActivity(), this);
} else {
//Else we are creating our Activity from scratch, pull list from where ever you initially get it from
ArrayList<YourDataType> list = getInitData();
ItemAdapter adapter = new MenuItemAdapter(list, getActivity(), this);
}
}
YourDataType
You didn't mention what YourDataType was but I'm assuming it's a custom class. In order to work with the bundled savedstate, it must implement Parcelable. Android's dev link on Parcelable and a StackOverFlow post explaining how to write your own custom class with Parcelable.
Update
Depending on what you are doing with the fragment will dictate if the onSavedInstanceState() method will be called. From the way your question is asked, I'm assuming you are pushing one fragment onto the backstack to load another on top. Then hitting the back button will reloaded that fragment from the backstack...and pass along the bundled state to reload from.
Of course that is just one of many scenarios. It's perfectly possible for the fragment in question to only perform onPause() followed by onStop()...then when redisplaying the fragment, only seeing it do onStart() followed by onResume(). In this case, there would not be a saved state because the fragment was never fully destroyed, so there is nothing to restore. It should be in the same state. If it's not and instead you are seeing it reload the initial data...then you are probably reloading the initial data in or after onStart(). You'll want to move all that initializing data to onCreate() instead.
Another possible case is that you completely destroy the fragment without ever putting it on the backstack. Re-initializing it would not have a saved state to pull from. If you wanted to "restore" this fragment back to the state beforehand, then you can not rely upon onSavedInstanceState(). You will need to persist that information manually somewhere in memory, to disk, or a DB yourself. Then pull from it accordingly.
Life cycles with fragments are unfortunately really complex and depend greatly on usage and even between the support library vs native fragments.
After doing some reading/research I ended up solving this by saving the adapter data to Saved Preferences by using Gson (https://code.google.com/p/google-gson/).
I used Gson to first convert my array of objects into Json and then stored the Json strings in the Saved Preferences.
Subsequently I was able to retrieve these as required and read the Json into Java objects again, store these in an array list and pass this to my listview adapter.
Here's a brief overview of the process for anyone who is looking to do something similar.
Saving to Shared Preferences:
SharedPreferences settings;
Editor editor;
settings = c.getSharedPreferences(PREFS_NAME,Context.MODE_PRIVATE);
editor = settings.edit();
Gson gson = new Gson();
String ItemsJson = gson.toJson(list);
editor.putString(categoryId, ItemsJson);
editor.apply();
Reading from Shared Preferences:
if (settings.contains(categoryId) {
String jsonItems = settings.getString(categoryId, null);
Gson gson = new Gson();
Item[] favoriteItems = gson.fromJson(jsonItems, Item[].class);
list = Arrays.asList(favoriteItems);
ItemsFromSharedPrefs = new ArrayList<>(list);
AllCategories.addAll(ItemsFromSharedPrefs);
} etc...

NullPointerException after resuming the application

Ok, this annoying problem is probably quite familiar, but I don't know what its being called and how to solve it. When I open my app and go to the menu and after a meanwhile, when resuming the app, I loose all my data. It seems that android likes to clean data in order to keep the OS as fast and stable as possible. Which method is recommended in saving data in the internal memory and retrieving it back when any kind of variable is cleaned/null before resuming the app? I tried setSharedPreferences to parse an ArrayList to an Object and parse the Object as a String to save the data and retrieve it, but I get cannot parse Object to String exception. There has to be a better alternative.
Any help will be appreciated.
Edit:
This is how I retrieve and store data:
JSONObject data = (JSONObject) new JSONTokener(result).nextValue();
Helper.RAW_PEOPLE_INFO = data.getJSONArray("people");
Helper.PEOPLE_CONTAINER = new ArrayList<PeopleInfoStorage>();
for( int i = 0; i < Helper.RAW_PEOPLE_INFO.length(); i++ ){
Helper.PEOPLE_CONTAINER.add( new PeopleInfoStorage(Helper.RAW_PEOPLE_INFO.getJSONObject(i)) );
}
I use the PEOPLE_CONTAINER ArrayList to use it later for when I need it. The PEOPLE_CONTAINER ArrayList gets probably cleaned before I resume my application, so can someone help me giving an example on how to store this ArrayList in the internal memory so I can retrieve the data from the internal memory and put it back to the PEOPLE_CONTAINER ArrayList for when it's null again.
It needs to be something like this:
#Override
protected void onPause() {
if( Helper.PEOPLE_CONTAINER != null ){
//save the Helper.PEOPLE_CONTAINER ArrayList to the internal memory
}
super.onPause();
}
#Override
protected void onResume() {
if( Helper.PEOPLE_CONTAINER == null ){
//retrieve the data and store it back to Helper.PEOPLE_CONTAINER ArrayList
}
super.onResume();
}
There are many methods to persist data in your application; I'm not going to go into great detail here, but you should check out these resources:
http://developer.android.com/guide/topics/data/data-storage.html
If you have an array list, it sounds like it might be worth SQLite.

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.

Cannot restore Object from Instance state: parsing error

I am trying to restore an array of Objects from a savedInstanceState. I added each one to the Bundle individually here: (rhythm is the array of Objects)
#Override
public void onSaveInstanceState(Bundle outState){
outState.putInt("numParts",rhythm.length);
for(int index = 0;index<rhythm.length;++index){
outState.putSerializable(""+index,rhythm[index].beat);
}
super.onSaveInstanceState(outState);
}
When the onRestoreInstanceState() method is called, I try to assign my rhythm array with the Objects from the Instance State here: (it isn't null)
#Override
public void onRestoreInstanceState(Bundle savedInstanceState){
rhythm = new Part[savedInstanceState.getInt("numParts")];
for(int index = 0; index<rhythm.length;++index){
Object middleMan =savedInstanceState.getSerializable(""+index);
if(middleMan==null){
System.out.println("It's null...");
}
rhythm[index]=(Part) middleMan;
}
}
It throws a ClassCastException when I parse to a Part every time. Part implements Serializable. Why is it not allowing me to parse? Will I need to do custom serialization?
Please help!
I am guessing that Part is a type that you have created? So instead of treating Part as an array
rhythm = new Part[savedInstanceState.getInt("numParts")];
You want to instantiate a new Part object like so:
rhythm = new Part(savedInstanceState.getInt("numParts"));
Other assumptions:
rhythm is a member variable
The constructor for Part takes a single integer
Okay I just did it as the whole array and it worked... I don't really know why, but it did. Thanks for giving me the idea to just pass the whole array. #Error 454

Categories

Resources