How to prevent SharedPreferences from loading corrupt data - android

I have a custom class with 3 String fields (arg1, arg2, arg3) and getters/setters. In Fragment there are 3 editText fields that create a new Object of custom class. And I also have an ArrayList of custom objects (max capacity is 15, but its usualy around 1-3) that is stored in SharedPreferences.
Everything worked fine until last update in which I only changed some UI design features in xml. Not a single line of Java code was changed, especialy those that have something to do with Custom objects, ArrayList or SharedPrefs.
Now some users, after updating my app, get corrupted first (or in most cases, only) entry of ArrayList, and arg1 is "" or null.
The worst thing is they are displayed the value of arg2 where is explicitly called textView.setText(arg1).
Im saving my SharedPrefs in onStop() and restoring it in onCreate() and I'm using Gson/Json for storing. In 99,9% everything is fine, but after recent update, some users reported this problem.
What's going on, and how to prevent it from happening in the future?
EDIT:
What about the fact that I set my ArrayList to be public static, so I can use it elsewhere, would it be a problem? Also since I can't debug production app, I've noticed that either args or their values have moved like MyCustomObject("val1", "val2", "val3") is now MyCustomObject("val2", "val3", "???") I dont know what last value is. Is it val1 or ""

Everything worked fine until last update in which I only changed some
UI design features in xml.
UI classes (like your custom UI class with 3 EditText) should not be serialized, because UI classes tend to change over time and it is not the pattern. You should only store/serialize data/object that represents UI states, not the UI objects themselves.
Im saving my SharedPrefs in onStop() and restoring it in onCreate()
and I'm using Gson/Json for storing. In 99,9% everything is fine, but
after recent update, some users reported this problem.
Suppose, the newer version of a UI class includes an extra member variable. Now, serializing in the newer version will give a different JSON string than the previous version. And as a result, your UI logic might conflict.
Workaround
In this case, you should make ArrayList of the custom class with 3 String fields (arg1, arg2, arg3) and getters/setters, then serialize/deserialize this ArrayList with Gson or any other library. Note that, the following example is basic and not perfect since no associated code provided.
In onCreate(), initialize the UI components with this data. For example:
// Retrieve data and initialize custom data holder by deserializing stored json
CustomObj customObj = gson.get(typeToken, str);
// Use this data to initialize UI components
customUiObj.editText1.setText(customObj.arg1);
customUiObj.editText2.setText(customObj.arg2);
customUiObj.editText3.setText(customObj.arg3);
In onStop(), retrieve data from the UI components to store. For example:
String arg1 = customUiObj.editText1.getText().toString();
String arg2 = customUiObj.editText2.getText().toString();
String arg3 = customUiObj.editText3.getText().toString();
// Now, create a data holder object
CustomObj obj = new CustomObj(arg1, arg2, arg3);
// Serialize this obj
String jsonStr = gson.toJson(customObj);

Related

RecycleView with data from JSONArray and the need to highlight the elements of the list: best practice

Good day.
I have an RecycleView list with data from JSONArray. Customer can select or de-select any item from this list by click on it.
So every RecycleView’s item can be in two states: marked (selected) or not.
How best to realize this opportunity? By creating a new separate array with additional “selected” boolean property and loading data into this array when creating a list as a RecycleView’s data source?
Or extend JSONArray with this property and use single data sourse (JSONArray)?
I have little programming experience under Android but see the following advantages and disadvantages:
JSONArray unhandled so needs to use try/catch construction at all
points of work with him;
JSONArray more visible in the code due to named elements like jsonList.put("selected", "false");
work with traditional java array must be faster;
any changes in source JSONArray (in fact, with each new request to the server) needs to full recreation of this ‘buffer’ separate java array.
I suggest you using separate array in adapter for holding item state. In my mind it more flexible when you not need to modify model and keep all logic inside adapter.
You can use different types depends on situation, not only traditional java arrays.
For example, mu solution to hold checked or not state for item in recyclerview:
Declare array to hold state:
SparseBooleanArray itemStateChecked= new SparseBooleanArray();
In onClick() put value for this item:
if (!itemStateChecked.get(item.id))
itemStateChecked.put(item.id, true);
else
itemStateChecked.delete(item.id);
In onBindViewHolder() restore state:
if (itemStateChecked.get(1))
mCheckView.setChecked(true);
else
mCheckView.setChecked(false);
SparseArray accept only primitive as key and SparseBooleanArray returns false if mapping not found.

Editing realm object relationship without committing transaction

I have a class called Person one of the variables is a realm list called clothes:
public class Person extends RealmObject{
private RealmList<Clothing> clothes;
}
Clothes is used to display in a RecyclerView wherein the contents can be edited such as quantity, color of clothing, or cloth type.
When I edit the clothing values:
public void setCloth(int i, Cloth cloth) {
realmInstance.beginTransaction();
clothes.set(i, cloth);
realmInstance.commitTransaction();
}
Of course, the record is saved. My problem is what if the user, cancels the editing of the WHOLE Person object. Then the clothes list change will persist.
Please help.
I'm sure you would be a great fan of nested transactions (https://github.com/realm/realm-java/issues/1509).
In the next release of Realm, a new method copyFromRealm() will be introduced. You should copy the clothes list and use copyToRealmOrUpdate() when you commit the Person object.
I have now solved the issue:
The solution is if you're using a fragment or an activity, and displaying the realm object and it's relationship through widgets, begin transaction immediately.
Realm realmInstance = Realm.getInstance(this);
realmInstance.beginTransaction();
This way, changes done directly on the realm object whether a relationship or a plain variable will be allowed.
To cancel the changes done simply use:
realmInstance.cancelTransaction();
This code can be invoked by overriding onBackPressed or any means of canceling the editing.
After canceling the transaction, any changes done including the RealmList objects will be forfeited.
More Info:
I was displaying the clothes list array in a recyleview through an adapter with editing controls (etc. setting the qty, changing cloth type). This solution can also be used in that scenario.
Hope this helps.

Restoring an ArrayList of custom objects

In my app (which is a game), I have an 'Enemy' class, for example like so:
public class Enemy extends Sprite implements Serializable {
public Enemy(EnemyType type){
super();
}
}
I have then declared an ArrayList like so:
ArrayList<Enemy> enemyList = new ArrayList<Enemy>();
To which I can add enemies:
enemyList.add(bird);
enemyList.add(bee);
When saving to the Bundle I simply put:
bundle.putSerializable("Enemies", enemyList);
And when restoring from the Bundle, I have this:
enemyList = (ArrayList<Enemy>) savedState.getSerializable("Enemies");
Now, it does seem to restore the arraylist (I can check it's size and it is always correct - ie, the same size on restoring from the bundle, as it was when saving to the bundle.
I have also logged for example, the first index of the ArrayList and sure enough it lists the enemy instance as being there.
However, if I try to manipulate the ArrayList at any time post-restoration, I get an exception telling me that I'm trying to perform [whatever action] on a Null object (enemyList).
If I simply populate the list myself, so have something like:
enemyList = (ArrayList<Enemy>) savedState.getSerializable("Enemies");
enemyList.add(bird);
enemyList.add(bee);
Then everything works as expected.
I'm assuming this has something to do with the fact that the super class of Enemy isn't serialised? However, if I serialise this, I get a 'notSerializableException' error.
Please note, I'm not really too worried about saving/restoring the actual Enemy objects to the Bundle, I can handle this manually. Rather I just want the list to be in the same state as it was. And I thought that what was stored in the ArrayList were just references to the objects in question, therefore I can't work out why this is happening?
Any ideas what I'm doing wrong or is there a better method to achieve that which I'm trying to achieve?
The recommended way of doing this in Android is to make the objects you want to persist to be Parcelable. This is a type of serialization specific to Android.
Have a look at the official documentation here

Is it inefficient to fetch again an object from the db in Android?

I come from the web world, I'm new to Android.
In the web world, you usually pass the id of things, from the "list view" to the "detail view", example:
URL: Messages/List --> Message/Detail?messageId=x
In Android, I have this activity with all my messages: MessagesActivity
They are fetched from the db (SQLite) and displayed in a ListView with a custom made Adapter.
Question: Is it inefficient to only pass the id of the message (in the intent extras) from this activity to MessageDetailActivity ?
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Intent intent = new Intent(thisActivityContext, MessageDetailActivity.class);
intent.putExtra("messageId", id); // inefficient? should pass object?
startActivity(intent);
}
As opposed to passing the entire message object.
I'm asking this because I'm not finding very intuitive the usage of a "Tag" in my view, that I would have to cast...
Also, holding all the "Message" objects in tags would use a lot of memory maybe
If your object is relatively light, you could make its class implement Serializable and pass it as en extra to the MessageDetailActivity, if you think the object is heavy (many fields and nested objects inside) you should pass the Id and fetch it again from the MessageDetailActivity.
Keep in mind though that fetching a record from Sqlite on Android is fast on most devices, so its ok to fetch a couple of records on your new activity (as long as you do it on a background thread to keep the UI from locking)
However one thing I've found is that if you need to modify that object on db, you will save yourself a lot of trouble if you just fetch the whole objet from DB everytime any of the activities is created, since that way you will avoid sync issues.

Android Beginner: Adapter for simple view or direct DB query?

First off I'm really new to android (< 4 days).
I'm trying to wrap my head around how database data is linked to Views and widgets.
I've done a few tutorials and I've noticed that Adapters are used to link AdapterViews which are (as I understand it) Views which contain a bunch of identical subviews (eg lists, gallery, etc). So the Adapter is responsible for creating those subviews and populating the data for each one (correct me if I'm wrong).
Now let's say I have a list view which lists Hotels for example. Each row in the list has the Hotel's name and a basic rating (eg 5 star). Now when you tap on a hotel in the list a new activity shows up showing the details of that particular hotel. All the data is in a database. I understand that you have an adapter manage the data<->view link for the list, but what's the best way to then manage data for the hotel details view (which is not a list but just a couple of text views and an image for example)?
Is it best to just pass the ID in the intent and then have the details activity fetch the data from the DB on its own (in this case do I store the query in the details activity?)? Or do you get all the fields you need and put those in the intent directly? Do you need an adapter for a view which doesn't actually generate lots of similar subviews?
I guess a summary of my question is what do you use instead of an adapter when you're not dealing with adapterviews but just simple straightforward single views.
Thanks for any help you can provide.
Is it best to just pass the ID in the intent and then have the details activity fetch the data from the DB
on its own (in this case do I store the query in the details activity?)?
That is what I would recommend.
Do you need an adapter for a view which doesn't actually generate lots of similar subviews?
No. You only use an Adapter with an AdapterView (ListView, Spinner, etc.)
I guess a summary of my question is what do you use instead of an adapter when you're not dealing with
adapterviews but just simple straightforward single views.
Just the Cursor from the database. Get the fields from the (one) row in the Cursor, put them in the EditText widgets, and when the user makes changes, update the row.
#CommonsWare is bang on. As a code example, I was able to DRY things up by creating a helper method to dynamically set the TextViews a little cleaner.
In my onCreate() I have a number of the following lines:
bindTextView(hotel, "uid"); // `hotel` is the Hotel object with attributes.
And then I define bindTextView() below as follows:
protected void bindTextView( Hotel hotel, String attribute ) {
try {
// Get field for object dynamically.
Field field = hotel.getClass().getField(attribute);
// Invoke field "getter" method to get value.
String value = field.get(hotel).toString();
// Get resource id dynamically.
int resourceId = R.id.class.getField(attribute).getInt(null);
// Get element with resource id.
TextView element = (TextView) this.findViewById( resourceId );
// Finally, set the element's text value.
element.setText( value );
} catch (IllegalAccessException e) { e.printStackTrace();
} catch (NoSuchFieldException e) { e.printStackTrace(); }
}
I actually moved this helper method to a base Activity class since it's shared amongst a number of different activities.
I hope that helps some people keep their activities clean.
JP

Categories

Resources