Serializing Android Views - causes InvalidClassException from IllegalAccessException - android

I'm trying to make an Android app that will store widgets (represented as Views) in a database, and then be able to recreate them later on.
Here is the gist of it:
public class DBSerializer<T extends View & Serializable> {
public int storeWidget(T widget) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bytes);
out.writeObject(widget);
byte[] data = bytes.toByteArray();
bytes.close();
out.close();
<store "data" in database>;
return <unique database id>;
}
public T getWidget(int id) {
byte[] data = <get data from database for the given id>;
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data));
return (T)in.readObject();
}
}
Unfortunately, the in.readObject() throws an InvalidClassException with the message android.view.View; IllegalAccessException.
Since Android's View directly extends Object, and Object has a no-argument constructor, shouldn't this work? Or is it trying to call View with a no-argument constructor? The error message is not being very clear about what the exact cause of the exception is.
Does anyone know what is causing this and how to fix it?

Just because the base class has a parameterless constructor, that does not mean that subclasses will. TextView does not, nor do most other views.
Serializing a View in its entirety and retrieving it later doesn't seem like a good idea, since a View has a context, which may have been destroyed in the time between you commit the serialized version and the time your retrieve it.
Perhaps you could store only the data you need in a Parcel, and use that to get a byte array, which you could then store in your database as a blob. Chances are, you might find this approach useful anyways in conjunction with the onSaveInstanceState() method.

Related

How to fetch object and array fields with Parse?

I'm unable to properly fetch a ParseObject that contains a field of type 'Object' : after changing manually the 'Object' field value in the Parse DataBrowser and then fetch the ParseObject from the app, the fetched ParseObject still provide the old value for the 'Object' field, but provide the right new value for the 'String' field.
Here is the sample code I use :
public class MainActivity extends ActionBarActivity {
ParseObject object;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
object = ParseObject.createWithoutData("Test", "tvgTg8jAXz");
}
#Override
protected void onResume() {
super.onResume();
object.fetchInBackground().onSuccess(new Continuation<ParseObject, Object>() {
#Override
public Object then(Task<ParseObject> task) throws Exception {
JSONObject data = task.getResult().getJSONObject("data");
String name = task.getResult().getString("name");
Log.d("OBJECT", data.toString());
Log.d("OBJECT", name);
return null;
}
}).continueWith(new Continuation<Object, Object>() {
#Override
public Object then(Task<Object> task) throws Exception {
if (task.getError() != null) {
Log.e("OBJECT", task.getError().getLocalizedMessage());
}
return null;
}
});
}
}
After I change both 'data' and 'name' fields in the DataBrowser, if 'onResume()' is called without a previous call to 'onCreate()' (after locking/unlocking screen for example) then the logs shows the old value for 'data' and the new value for 'name'.
This is a simple code example to highlight the problem I encounter in a bigger project.
Is this a known issue of the Parse Android SDK ? Is there a workaround ?
Thanks
Now that I learned that you have turned on the local datastore I can come with an, at least partial, answer.
Turning on the local datastore has some side effects. One being that only one instance of each object exists locally. So when you call fetchInBackground the second time, object is already populated with data. The problem then (i think) is that the API no longer override 'complex' types (pointers, objects, arrays), perhaps because it could mess up internal relationships in the data store. Since the fact that the data store will recursively save an object (and pointers) so suddenly swapping a pointer might leave objects 'hanging'. (again, only guessing).
Now I must admit that it still confuses me a bit looking at your code, cause it does not seem that you at any point write your object to the data store, however..
What should work is to unpin the object before 'refreshing' it:
object.unpinInBackground.onSuccess(new Continuation<>{
...
// when done call fetch
});
According to Parse, this is a known issue that they will not fix for now : https://developers.facebook.com/bugs/1624269784474093/
We must use the following methods to retrieve JSON objects/arrays fields from a ParseObject :
getMap() instead of getJSONObject()
getList() instead of getJSONArray()
These methods will return Map and List objects respectively.
I found that managing Map and List in my project instead of JSONObjet and JSONArray is not a problem and is even clearer.

Saving data upon closing app and retrieving that data

I know, there are plenty of questions in regards to saving/retrieving data on here. I was doing find looking things up on my own and really thought I could manage to find my answers without having to "ask a question", but I began to wonder something that I haven't seen an answer for on here.
MY SITUATION:
Naturally, I'm making an app. Upon closing the app, I want to save a simple array of numbers (0 or 1) or boolean values as it were. Upon starting the app, I want to search for that array, if it exists, and retrieve it for use within the app.
I began placing my code into the activity in which the array would be used. But, I started wondering if I would have to copy/paste the overridden onStop() function into all of my activities? Or do I do it in the main activity and somehow link the other activities.
Basically, no matter what state/activity the app is currently on when the app is closed, I want to be able to save the array of int/bool and open it back up when the app is started.
Maybe I didn't know how to search for what I wanted, so explaining it felt like the right thing to do.
I don't mind doing more searching, but if someone would point me in the right direction at the very least, I'd be extremely grateful.
EDIT: If there's a better way to do what I want than what I described (i.e. using a different state instead of onStop(), for instance), please feel free to throw out ideas. This is my first time actually having to deal with the activities' lifecycles and I'm a bit confused even after looking through the android development tutorials. I really think they're poorly done in most cases.
When you application needs to save some persistent data you should always do it in onPause() method and rather than onStop(). Because if android OS kills your process then onStop() and onDestroy() methods are never called. Similarly retrieve data in onResume() method.
Looking at the purpose you want to fulfill, SharedPreferences is all you want.
The documentation states:
"SharePreferences provides a general framework that allows you to save
and retrieve persistent key-value pairs of primitive data types. You
can use SharedPreferences to save any primitive data: booleans,
floats, ints, longs, and strings. This data will persist across user
sessions (even if your application is killed)."
Use SharedPreference to store small amount of data or use SQLite to store large amount of data.
See this link
http://developer.android.com/guide/topics/data/data-storage.html
Serialize an object and pass it around which is more dependable than shared preferences (had lots of trouble with consistency with shared preferences):
public class SharedVariables {
public static <S extends Serializable> void writeObject(
final Context context, String key, S serializableObject) {
ObjectOutputStream objectOut = null;
try {
FileOutputStream fileOut = context.getApplicationContext().openFileOutput(key, Activity.MODE_PRIVATE);
objectOut = new ObjectOutputStream(fileOut);
objectOut.writeObject(serializableObject);
fileOut.getFD().sync();
} catch (IOException e) {
Log.e("SharedVariable", e.getMessage(), e);
} finally {
if (objectOut != null) {
try {
objectOut.close();
} catch (IOException e) {
Log.e("SharedVariable", e.getMessage(), e);
}
}
}
}
Then use a class to use:
public class Timestamps implements Serializable {
private float timestampServer;
public float getTimestampServer() {
return timestampServer;
}
public void setTimestampServer(float timestampServer) {
this.timestampServer = timestampServer;
}
}
Then wherever you want to write to the variable use:
SharedVariables.writeObject(getApplicationContext(), "Timestamps", timestampsData);
Best way to achieve that is:
create a class. Call it MySettings, or whatever suits you
in this class, define the array of ints / booleans you are going to share, as static. Create getter & setter method (property) to access that (also as static methods)
add a static load() method to MySettings that reads from SharedPreferences. When you launch the app (in your first activity or better in a subclass of Application) call MySettings.load(). This load method sets the array
add a static save() method. Public also. Now you can save from anywhere in you app. This save() method reads the array and writes in SharedPreferences
Code sample:
public class MySettings {
private static List<Integer> data;
public static void load() {
data = new ArrayList<Integer>();
// use SharedPreferences to retrieve all your data
}
public static void save() {
// save all contents from data
}
public static List<Integer> getData() {
return data;
}
public static void setData(List<Integer> data) {
MySettings.data = data;
}
}

Using serialisable object from internal storage leads to memory leak? how to avoid

I searched a solution but I didn't find it. I write an ArrayList containing my Name object to a file, and the retrieved ArrayList will later be used by different activities for the same purpose. This object is a separate class in a separate package.
I write the ArrayList with ObjectOutputStream and then close the FileOutpoutStream and ObjectOutputStream. I do it many times in an activity 's lifetime.
The object is like:
public class Name implements Serializable{
private static final long serialVersionUID = 7526472295622776147L;
String name;
int id;
public Name (String name, int id){
this.name = name;
this.id = id;
}
public String getName(){
return this.name;
}
public String getId(){
return this.id;
}
When I want to retrieve it, I create a new ArrayList of Name object and use ObjectInputStream to add elements by doing
ArrayList<Name> array = new ArrayList<Name>();
array.addAll((ArrayList<Name>)ois.readObject());
I avoided doing
array = (ArrayList<Name>) ois.readObject();
I thought there will be no reference this way.
I then close the FileInputStream and ObjectInputStream. I do this many times in an activity's lifecycle as well.
When I check MAT with eclipse, the histogram shows that memory contains many Name objects and thus, block the activities to destroy themself.
I tried to reset() Streams before closing them, I tried the opposite too, tried to only reset() whithout closing, I tried the same onDestroy(), but every actions lead to Exceptions and unwanted behaviors.
Changing the serial doesn't work to, it doesn't write or gives Exceptions so it seems that the serial must remain static and must not be removed.
How should I deal whith this, considering that this ArrayList must be stocked on the storage and shared with different activities? Should I use SharedPreferences instead or something else?

Passing a custom Object from one Activity to another Parcelable vs Bundle

I'd like to pass a custom Object from one activity to another, the Object consists of a String and a List of another custom Object which consists of an array of strings and an array of ints. I've read https://stackoverflow.com/a/2141166/830104, but then I've found this answer https://stackoverflow.com/a/7842273/830104. Which is better to use Bundle or Parcelable? What is the difference? When should I use this each? Thanks for your replies, Dan
Parcelable and Bundle are not exclusive concepts; you can even deploy both on your app at a time.
[1] Term Parcelable comes with Serialization concept in Java (and other high-level language such as C#, Python,...). It ensures that an object - which remains in RAM store - of such Parcelable class can be saved in file stream such as text or memory (offline status) then can be reconstructed to be used in program at runtime (online status).
In an Android application, within 2 independent activities (exclusively running - one starts then other will have to stop):
There will be NO pointer from current activity to refer to previous one and its members - because previous activity is stopped and cleared out form memory; so that to maintain object's value passed to next activity (called from Intent) the object need to be parcelable (serializable).
[2] While Bundle is normally the Android concept, denotes that a variable or group of variables. If look into lower level, it can be considered as HashMap with key-value pairs.
Conclusion:
Bundle is to store many objects with related keys, it can save any object in native types, but it doesn't know how to save a complex object (which contains an ArrayList for example)
Parcelable class is to ensure a complex instance of it can be serialized and de-serialized during runtime. This object can contains complex types such as ArrayList, HashMap, array, or struct,...
[UPDATED] - Example:
//Class without implementing Parcelable will cause error
//if passing though activities via Intent
public class NoneParcelable
{
private ArrayList<String> nameList = new ArrayList<String>();
public NoneParcelable()
{
nameList.add("abc");
nameList.add("xyz");
}
}
//Parcelable Class's objects can be exchanged
public class GoodParcelable implements Parcelable
{
private ArrayList<String> nameList = new ArrayList<String>();
public GoodParcelable()
{
nameList.add("Can");
nameList.add("be parsed");
}
#Override
public int describeContents()
{
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags)
{
// Serialize ArrayList name here
}
}
In source activity:
NoneParcelable nonePcl = new NoneParcelable();
GoodParcelable goodPcl = new GoodParcelable();
int count = 100;
Intent i = new Intent(...);
i.putExtra("NONE_P",nonePcl);
i.putExtra("GOOD_P",goodPcl);
i.putExtra("COUNT", count);
In destination activity:
Intent i = getIntent();
//this is BAD:
NoneParcelable nP = (NoneParcelable)i.getExtra("NONE_P"); //BAD code
//these are OK:
int count = (int)i.getExtra("COUNT");//OK
GoodParcelable myParcelableObject=(GoodParcelable)i.getParcelableExtra("GOOD_P");// OK

Bundle of array of arraylist

How can I put an array of arrayList into a Bundle?
ArrayList < myObjects >[] mElements;
Make YourObject implement the Parcelable interface, then use bundle.putParcelableArraylist(theParcelableArraylist).
Edit: whoops misread the question. Why do you want to save an array of arraylist? If its for persisting in an orientation change, maybe you want to use onRetainNonConfigurationInstance instead?
Edit 2: Ok, here goes. Create a new Wrapper class that implements Parcelable (note that myObjects also have to be parcelable). The writeToParcel method will look something like this:
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mElements.length);
for(ArrayList<MyObject> element : mElements) {
dest.writeParcelableArray(element.toArray(new MyObject[0]), flags);
}
}
And the constructor:
private Wrapper(Parcel in) {
int length = in.readInt();
//Declare list
for (int i = 0; i < length; i++) {
MyObject[] read = in.readParcelableArray(Wrapper.class.getClassLoader());
//add to list
}
}
Not possible using bundle, as bundle allows arraylist of primitives only...
Try to use parcleable or application level data or static variable (bad practice).
If your objects support serialization, marked with the Serializable interface, then you should be able to use bundle.putSerializable.
ArrayList supports Serializable , but I'm not sure about a plain array.
I just use putSerializable(myarraylistofstuff) and then I get back with a cast using get(), you just need to silence the unchecked warning. I suspect (correct me if wrong) you can pass any object faking it as Serializable, as long you stay in the same process it will pass the object reference. This approach obviously does not work when passing data to another application.
EDIT: Currently android passes the reference only between fragment, I've tried to pass an arbitrary object to an Activity, it worked but the object was different, same test using arguments of Fragment showed same Object instead.
Anyway it deserializes and serializes fine my Object, if you have a lot of objects it's better to use Parcel instead

Categories

Resources