How to permanently store parcelable custom object? [duplicate] - android

This question already has answers here:
Save Bundle to SharedPreferences [duplicate]
(3 answers)
Closed 10 years ago.
I want to store a custom object (let's call it MyObject) permanently so that if it is deleted from memory, I can reload it in my Activity/Fragment onResume() method when the app starts again.
How can I do that? SharedPreferences doesn't seem to have a method for storing parcelable objects.

If you need to store it in SharedPreferences, you can parse your object to a json string and store the string.
private Context context;
private MyObject savedObject;
private static final String PREF_MY_OBJECT = "pref_my_object";
private SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
private Gson gson = new GsonBuilder().create();
public MyObject getMyObject() {
if (savedObject == null) {
String savedValue = prefs.getString(PREF_MY_OBJECT, "");
if (savedValue.equals("")) {
savedObject = null;
} else {
savedObject = gson.fromJson(savedValue, MyObject.class);
}
}
return savedObject;
}
public void setMyObject(MyObject obj) {
if (obj == null) {
prefs.edit().putString(PREF_MY_OBJECT, "").commit();
} else {
prefs.edit().putString(PREF_MY_OBJECT, gson.toJson(obj)).commit();
}
savedObject = obj;
}
class MyObject {
}

You can write your Bundle as a parcel to disk, then get the Parcel later and use the Parcel.readBundle() method to get your Bundle back.

Related

Solution to ArrayIndexOutOfBoundsException when restoring RecyclerView state with ArrayList of custom objects and Gson

I have a RecyclerView in which the views/data in the ViewHolders (image, title, description) are retrieved with the getter methods of a custom object Challenge.
I also have an ArrayList of type Challenge (ArrayList<Challenge> mChallenges) where I add all the challenges and I later pass this ArrayList as a parameter of my RecyclerView.Adapter subclass when I initialize it.
The ViewHolders of my RecyclerView also have a checkbox so users can select the challenges they want, and these chosen challenges are saved on a different ArrayList<Challenge> named currentSelectedChallenges. This is how they get added.
#Override
public void onChallengeChecked(int position) { // method of interface
if (!currentSelectedChallenges.contains(mChallenges.get(position))){
currentSelectedChallenges.add(mChallenges.get(position));
}
}
When the user leaves the fragment where the RecyclerView is contained, either with the back button or closing the app, I save the selected challenges ArrayList<Challenge> currentSelectedChallenges with a SharedPreferences using Gson like this:
#Override
public void onPause() {
super.onPause();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences.Editor editor = sharedPreferences.edit();
Gson gson = new Gson();
Type type = new TypeToken<ArrayList<Challenge>>(){}.getType();
String json = gson.toJson(currentSelectedChallenges, type);
editor.putString("selected challenges", json);
editor.apply();
}
And I restore currentSelectedChallenges on the onRestore method of the fragment in the following way:
#Override
public void onResume() {
super.onResume();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
Gson gson = new Gson();
String json = sharedPreferences.getString("selected challenges", null);
Type type = new TypeToken<ArrayList<Challenge>>() {
}.getType();
currentSelectedChallenges = gson.fromJson(json, type);
if (currentSelectedChallenges == null) {
currentSelectedChallenges = new ArrayList<>();
} else
{
for (int i = 0; i < currentSelectedChallenges.size(); i++) {
int index = mChallenges.indexOf(currentSelectedChallenges.get(i));
mChallenges.get(index).setChecked(true);
}
mChallengesAdapter.notifyDataSetChanged();
}
}
When I try to restore the checked state of the CheckBox in the ViewHolder I get an ArrayIndexOutOfBoundsExceptions because currentSelectedChallenges.get(i) is not found inside mChallenges, so it returns -1. I have logged the challenges and the objects have different ids even though I can use the setter methods and will retrieve the same data.
I tried to trick Android and used this:
for (int i=0; i < currentSelectedChallenges.size(); i++) {
for (Challenge challenge : mChallenges) {
if (challenge.getName().equals(currentSelectedChallenges.get(i).getName())){
challenge.setChecked(true);
}
}
mChallengesAdapter.notifyDataSetChanged();
And it does tick in the UI the challenges that were checked by the user but then when I click again on the CheckBox it doesn't detect my click, so I know this is not the proper solution.
Could anyone explain how should I tackle this correctly?
mChallenges.indexOf(currentSelectedChallenges.get(i))
returns -1 because it compares reference instead of your interested data.
you should override equals and hashCode of Challenge class:
class Challenge{
// your current code
#Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Challenge)) {
return false;
}
Challenge challenge = (Challenge) o;
return challenge.name.equals(name) /*&& compare other values for equality*/;
// also check for null name if it is necessary
}
#Override
public int hashCode() {
int result = 17;
result = 31 * result + name.hashCode();
//result = 31 * result + age; // other property
//result = 31 * result + passport.hashCode();// other property
return result;
}
}

Cannot read List with Gson afer writing

I want to save and read List<MyObj> to sharedPreferences using Gson.
This is my write method:
private static final String GAS_STATIONS_LIST_KEY = "gasStationsListKey";
#Override
public void save(#NonNull List<MyObj> gasStations) {
saveStr(GAS_STATIONS_LIST_KEY, gson.toJson(gasStations));
}
private void saveStr(#NonNull String key, #Nullable String value) {
sharedPreferences
.edit()
.putString(key, value)
.apply();
}
And this is my read method:
#Override
public List<MyObj> getGasStationList() {
final Type type = new TypeToken<List<MyObj>>() {
}.getClass();
final List<MyObj> gasStations = gson.fromJson(GAS_STATIONS_LIST_KEY, type); // here null
if (gasStations != null && !gasStations.isEmpty()) {
return gasStations;
} else {
return new ArrayList<>();
}
}
But when I try read data I get null (comment in last code part).
How to fix it?
You are not getting the saved json content from shared prefences. You are trying to deserialize the key to a list, not the json content which is saved with that key.
Change this:
final List<MyObj> gasStations = gson.fromJson(GAS_STATIONS_LIST_KEY, type);
To this:
String savedJsonContent = sharedPreferences.getString(GAS_STATIONS_LIST_KEY, null);
final List<MyObj> gasStations = gson.fromJson(savedJsonContent , type);
SharedPreferences only store primitive data Types.

How to save Object (includes Array) in Preferences?

How to save Object in Preferences which looks like this:
public class ToDoList {
public String name;
public String date;
public ArrayList<Product> products = new ArrayList<Product>();
public boolean isChecked;
}
and then loads its values?
You could do it with serialization. Serialization of an object is a short unique String format of an object that can be serialized. Particularly almost every object can be serialized in java except from the View object. You won't have any problem in your case.
How to do it:
You should make class ToDoList implement Serializable and all classes that are used inside your object, ex Product. String, boolean ArrayList are serializable so you don't have to do anything.
When implementing serialization in an object you have to supply a serial version UID which would be then used to serialize.
So ToDoList would be something like:
public class ToDoList implelements Serializable {
private static final long serialVersionUID = //generate random by eclipse
.....
public ArrayList<Product> products = new ArrayList<Product>();
}
and Product:
public class Product implelements Serializable {
private static final long serialVersionUID = //generate random by eclipse
.....
}
then include this static helper class:
public class ObjectSerializeDeserialize {
public static String ObjectSerialization(Object obj)
{
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
try
{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArray);
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
catch (Exception e) {
e.printStackTrace();
return "";
}
return new String(Base64.encode(byteArray.toByteArray(), 0));
}
public static Object ObjectDeserialization(String str)
{
byte[] byteArray = Base64.decode(str,0);
Object o;
try
{
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArray));
o = ois.readObject();
ois.close();
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
return o;
}
}
and simply use the following code:
String todolistSer = ObjectSerializeDeserialize.ObjectSerialization(todolistObj);
the above line of code will return an empty String if something goes wrong and will print the detailed message in the log cat.
Then simply save the todolistSer as a String in preferences and reclaim your object like this:
ToDoList todolistObj = (ToDoList) ObjectSerializeDeserialize.ObjectDeserialization(todolistString);
suppress any warnings that are issued by the above method and you are done!
P.S. you can use the above solution whenever you have complicated structures that can not be saved as raw variables and you still don't want to use a database
Preferences are simple key ,value pairs. In your case better use SQLite.

android: sharedpreferences possible to use across activities?

i have created a sharedpreferences data in one activity, is it possible to be used in another activity? if yes, how could this be achieved?
The names of the 4 players are saved in NameIndex.java, and I would like to use the saved Names of the 4 players in the MainActivity.java
Under NameIndex.java:
private void SaveNamesToFile(String Game1, String P1Name, String P2Name, String P3Name, String P4Name)
// save the new row to the file, then refresh all Buttons
{
// originalScore will be null if we're modifying a slot that is existing already
String originalNameP1 = SavedNameP1.getString(Game1, null); // to return null if this preference does not exist.
String originalNameP2 = SavedNameP2.getString(Game1, null);
String originalNameP3 = SavedNameP3.getString(Game1, null);
String originalNameP4 = SavedNameP4.getString(Game1, null);
// get a SharedPreferences.Editor to store new row data
SharedPreferences.Editor preferencesEditorP1 = SavedNameP1.edit();
SharedPreferences.Editor preferencesEditorP2 = SavedNameP2.edit();
SharedPreferences.Editor preferencesEditorP3 = SavedNameP3.edit();
SharedPreferences.Editor preferencesEditorP4 = SavedNameP4.edit();
preferencesEditorP1.putString(Game1, P1Name);
preferencesEditorP2.putString(Game1, P2Name);
preferencesEditorP3.putString(Game1, P3Name);
preferencesEditorP4.putString(Game1, P4Name);
preferencesEditorP1.apply();
preferencesEditorP2.apply();
preferencesEditorP3.apply();
preferencesEditorP4.apply();
}
I used one SharedPreferences file between activities, but what I did was using the same file name declared in different private variables inside the two activities. You can check my code in the following link. What I don't understand is why you use 4 SharedReferences files just for the names of the players and not all the names in just 1 file. That's possible because I used it to save more than 2 variables.
Yes they can be shared across activities. The easiest route is to just use:
context.getDefaultSharedPreferences()
I am using it like this
public class SharedPreferencesHelper {
SharedPreferences myPrefs;
SharedPreferences.Editor prefsEditor;
private static SharedPreferencesHelper instance = null;
public static synchronized SharedPreferencesHelper getInstance() {
if (instance == null) {
instance = new SharedPreferencesHelper();
}
return instance;
}
private SharedPreferencesHelper() {
myPrefs = MyApplication.getInstanse().getApplicationContext().getSharedPreferences("prefs", Context.MODE_WORLD_READABLE);
prefsEditor = myPrefs.edit();
}
public void putValueForKey(String key, String value) {
prefsEditor.putString(key, value);
prefsEditor.commit();
}
}
public class MyApplication extends Application {
private static MyApplication instance;
#Override
public void onCreate() {
super.onCreate();
instance = this;
}
public static MyApplication getInstanse(){
if(instance ==null){
throw new IllegalStateException("Application not created yet!");
}
return instance;
}
}

How to serialize a Bundle?

I'd like to serialize a Bundle object, but can't seem to find a simple way of doing it. Using Parcel doesn't seem like an option, since I want to store the serialized data to file.
Any ideas on ways to do this?
The reason I want this is to save and restore the state of my activity, also when it's killed by the user. I already create a Bundle with the state I want to save in onSaveInstanceState. But android only keeps this Bundle when the activity is killed by the SYSTEM. When the user kills the activity, I need to store it myself. Hence i'd like to serialize and store it to file. Of course, if you have any other way of accomplishing the same thing, i'd be thankful for that too.
Edit:
I decided to encode my state as a JSONObject instead of a Bundle. The JSON object can then be put in a Bundle as a Serializable, or stored to file. Probably not the most efficient way, but it's simple, and it seems to work ok.
storing any Parcelable to a file is very easy:
FileOutputStream fos = context.openFileOutput(localFilename, Context.MODE_PRIVATE);
Parcel p = Parcel.obtain(); // i make an empty one here, but you can use yours
fos.write(p.marshall());
fos.flush();
fos.close();
enjoy!
I use SharedPreferences to get around that limitation, it uses the same putXXX() and getXXX() style of storing and retrieving data as the Bundle class does and is relatively simple to implement if you have used a Bundle before.
So in onCreate I have a check like this
if(savedInstanceState != null)
{
loadGameDataFromSavedInstanceState(savedInstanceState);
}
else
{
loadGameDataFromSharedPreferences(getPreferences(MODE_PRIVATE));
}
I save my game data to a Bundle in onSaveInstanceState(), and load data from a Bundle in onRestoreInstanceState()
AND
I also save game data to SharedPreferences in onPause(), and load data from SharedPreferences in onResume()
onPause()
{
// get a SharedPreferences editor for storing game data to
SharedPreferences.Editor mySharedPreferences = getPreferences(MODE_PRIVATE).edit();
// call a function to actually store the game data
saveGameDataToSharedPreferences(mySharedPreferences);
// make sure you call mySharedPreferences.commit() at the end of your function
}
onResume()
{
loadGameDataFromSharedPreferences(getPreferences(MODE_PRIVATE));
}
I wouldn't be surprised if some people feel this is an incorrect use of SharedPreferences, but it gets the job done. I have been using this method in all my games (nearly 2 million downloads) for over a year and it works.
Convert it to SharedPreferences:
private void saveToPreferences(Bundle in) {
Parcel parcel = Parcel.obtain();
String serialized = null;
try {
in.writeToParcel(parcel, 0);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.write(parcel.marshall(), bos);
serialized = Base64.encodeToString(bos.toByteArray(), 0);
} catch (IOException e) {
Log.e(getClass().getSimpleName(), e.toString(), e);
} finally {
parcel.recycle();
}
if (serialized != null) {
SharedPreferences settings = getSharedPreferences(PREFS, 0);
Editor editor = settings.edit();
editor.putString("parcel", serialized);
editor.commit();
}
}
private Bundle restoreFromPreferences() {
Bundle bundle = null;
SharedPreferences settings = getSharedPreferences(PREFS, 0);
String serialized = settings.getString("parcel", null);
if (serialized != null) {
Parcel parcel = Parcel.obtain();
try {
byte[] data = Base64.decode(serialized, 0);
parcel.unmarshall(data, 0, data.length);
parcel.setDataPosition(0);
bundle = parcel.readBundle();
} finally {
parcel.recycle();
}
}
return bundle;
}
In case you want to store it in persistent storage you can't rely on parcelable nor serializable mechanism. You have to do it by yourself and below is the way how I usually do it:
private static final Gson sGson = new GsonBuilder().create();
private static final String CHARSET = "UTF-8";
// taken from http://www.javacamp.org/javaI/primitiveTypes.html
private static final int BOOLEAN_LEN = 1;
private static final int INTEGER_LEN = 4;
private static final int DOUBLE_LEN = 8;
public static byte[] serializeBundle(Bundle bundle) {
try {
List<SerializedItem> list = new ArrayList<>();
if (bundle != null) {
Set<String> keys = bundle.keySet();
for (String key : keys) {
Object value = bundle.get(key);
if (value == null) continue;
SerializedItem bis = new SerializedItem();
bis.setClassName(value.getClass().getCanonicalName());
bis.setKey(key);
if (value instanceof String)
bis.setValue(((String) value).getBytes(CHARSET));
else if (value instanceof SpannableString) {
String str = Html.toHtml((Spanned) value);
bis.setValue(str.getBytes(CHARSET));
} else if (value.getClass().isAssignableFrom(Integer.class)) {
ByteBuffer b = ByteBuffer.allocate(INTEGER_LEN);
b.putInt((Integer) value);
bis.setValue(b.array());
} else if (value.getClass().isAssignableFrom(Double.class)) {
ByteBuffer b = ByteBuffer.allocate(DOUBLE_LEN);
b.putDouble((Double) value);
bis.setValue(b.array());
} else if (value.getClass().isAssignableFrom(Boolean.class)) {
ByteBuffer b = ByteBuffer.allocate(INTEGER_LEN);
boolean v = (boolean) value;
b.putInt(v ? 1 : 0);
bis.setValue(b.array());
} else
continue; // we do nothing in this case since there is amazing amount of stuff you can put into bundle but if you want something specific you can still add it
// throw new IllegalStateException("Unable to serialize class + " + value.getClass().getCanonicalName());
list.add(bis);
}
return sGson.toJson(list).getBytes(CHARSET);
}
} catch (Exception e) {
e.printStackTrace();
}
throw new IllegalStateException("Unable to serialize " + bundle);
}
public static Bundle deserializeBundle(byte[] toDeserialize) {
try {
Bundle bundle = new Bundle();
if (toDeserialize != null) {
SerializedItem[] bundleItems = new Gson().fromJson(new String(toDeserialize, CHARSET), SerializedItem[].class);
for (SerializedItem bis : bundleItems) {
if (String.class.getCanonicalName().equals(bis.getClassName()))
bundle.putString(bis.getKey(), new String(bis.getValue()));
else if (Integer.class.getCanonicalName().equals(bis.getClassName()))
bundle.putInt(bis.getKey(), ByteBuffer.wrap(bis.getValue()).getInt());
else if (Double.class.getCanonicalName().equals(bis.getClassName()))
bundle.putDouble(bis.getKey(), ByteBuffer.wrap(bis.getValue()).getDouble());
else if (Boolean.class.getCanonicalName().equals(bis.getClassName())) {
int v = ByteBuffer.wrap(bis.getValue()).getInt();
bundle.putBoolean(bis.getKey(), v == 1);
} else
throw new IllegalStateException("Unable to deserialize class " + bis.getClassName());
}
}
return bundle;
} catch (Exception e) {
e.printStackTrace();
}
throw new IllegalStateException("Unable to deserialize " + Arrays.toString(toDeserialize));
}
You represent data as byte array which you can easily store to file, send via network or store to sql database using ormLite as follows:
#DatabaseField(dataType = DataType.BYTE_ARRAY)
private byte[] mRawBundle;
and SerializedItem:
public class SerializedItem {
private String mClassName;
private String mKey;
private byte[] mValue;
// + getters and setters
}
PS: the code above is dependent on Gson library (which is pretty common, just to let you know).

Categories

Resources