How to use onSaveInstanceState method with Arraylist - android

I have built an ArrayList with an API results. I don`t want it to disappear when a user rotates his phone.
What should I do with onSaveInstanceState?
JSONObject json = new JSONObject(result);
JSONArray searchArray = json.getJSONArray("Search");
for (int i = 0; i < searchArray.length(); i++) {
JSONObject searchObject = searchArray.getJSONObject(i);
String title = searchObject.getString("Title");
String type = searchObject.getString("Type");
String year = searchObject.getString("Year");
String imdbID = searchObject.getString("imdbID");
movieList.add(new Movie(title, type, year, imdbID));

Assuming the arrayList you want to keep track of is called myArrayList
This code in the activity will serve your purpose :
For String Array :
ArrayList myArrayList;
//put value of myArrayList in some other method
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putStringArrayList("myArrayList", myArrayList);
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
myArrayList = savedInstanceState.getStringArrayList("myArrayList");
}
For Custom Object Array :
Make the objects' class implement the interface Parcelable and then add the objects to the ArrayList. In your case, make the Movie class implement Parcelable and add the objects to movieList.
Then use the same methods and instead of putStringArrayList() use :
outState.putParcelableArrayList("movieList", movieList);
Similarly, use :
movieList = (ArrayList<Movie>) savedInstanceState.getParcelableArrayList("movieList");
in the onRestoreInstanceState() method to get the array back.

Related

Why does ArrayAdapter fail to update listview after activity restart?

I have an activity that in onCreate() does the following:
Creates an empty ArrayList
Creates a new ArrayAdapter associated with the above ArrayList
Sets ListView to use the above ArrayAdapter
Uses Volley to send a GET request to my API to fetch some JSON data to load into the ListView
Once the data is fetched I add it to my ArrayList and the ListView is populated as expected
My problem is that when the activity is restarted (i.e. the screen is rotated via the emulator or the activity is restarted through Android Studio) the ListView no longer populates.
I am not saving any state. I expect the activity to return to its initial default state so I don't think onSaveInstanceState() is the answer.
I've verified that the data is returned successfully from the API and that the adapter's hashcode is the same before and after the volley request and that it equals the ListView's set adapter. I've also verified that onDestroy() and then onCreate() are called when the activity is restarted so I know it is going through a full life cycle.
If I rotate the screen programmatically with setRequestedOrientation() I don't experience this issue. If I add items to my ArrayList outside of the GET request callback, I don't experience this issue.
Here is my activity onCreate()
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
//The data to be displayed
descriptions = new ArrayList<>();
listView = (ListView)this.findViewById(R.id.myListView);
//Link 'descriptions' to the adapter
adapter = new ArrayAdapter<>(this, R.layout.list_json_text_view, descriptions);
listView.setAdapter(adapter);
this.addTextFilter();
this.addListViewClickListener();
//See my ApiGetRequest class below
request = new ApiGetRequest();
request.send(this.getContext(), getDataUrl(), this, "", REQUEST_TYPES.TEXT);
}
And my activity GET request callback
public void onSuccess(DescriptiveJSONArray items, REQUEST_TYPES type) {
descriptions.clear();
try {
for (int i = 0; i < items.length(); ++i) {
JSONObject obj = items.getJSONObject(i);
String desc = obj.optString("name", "") + " " + obj.optString("description", "");
//TODO: Remove debug code
System.out.println("Adding: "+desc);
descriptions.add(desc);
}
}
catch(JSONException e) {
e.printStackTrace();
//getJSONObject failed
}
}
And my ApiGetRequest methods
//My activity implements ApiGetCallback
public void send(Context context, String url, ApiGetCallback callback, String tag, REQUEST_TYPES type) {
StringRequest stringRequest = getStringRequest(url, callback, tag, type);
//Singleton wrapper for RequestQueue
AppRequestQueue queue = AppRequestQueue.getInstance(context);
queue.add(stringRequest);
}
//Inner class inside ApiGetCallback
class SuccessListener implements Response.Listener<String> {
ApiGetCallback callback;
REQUEST_TYPES type;
public SuccessListener(ApiGetCallback callback, REQUEST_TYPES type) {
this.callback = callback;
this.type = type;
}
#Override
public void onResponse(String response) {
try {
DescriptiveJSONArray jsonResp = new DescriptiveJSONArray(response);
callback.onSuccess(jsonResp, type);
}
catch(JSONException e) {
callback.onJsonException(e);
}
}
}
Any ideas what is happening?. I'm testing on Marshmallow and Nougat
You are missing a call to notifyDataSetChanged, after the onSuccess function is done.
you may need to override onStart and do update anything in it
adapter = new ArrayAdapter<>(this, R.layout.list_json_text_view, descriptions);
listView.setAdapter(adapter);
//See my ApiGetRequest class below
request = new ApiGetRequest();
request.send(this.getContext(), getDataUrl(), this, "", REQUEST_TYPES.TEXT);
use this part of code in onResume method.

Making JSONObject parcelable

I have an array list of JSON Objects that I'm trying to pass like so.
ArrayList<JSONObject> entryCities = new ArrayList<>();
for(int i = 0; i < entries.length(); i++){
entryCities.add(entries.getJSONObject(i));
}
Intent intent = new Intent(UnitedStatesActivity.this, StateActivity.class);
intent.putExtra("object", entryCities);
startActivity(intent);
I know this doesn't work and that I need to make my array, or the JSON Object in the array parcelable. I started looking at the documentation and have come up with this class that I am wanting to pass the ArrayList<JSONObject> into, but I'm not sure what I'm supposed to be adding in my writeToParcel() method.
Am I writing out the specific values of the JSON Object I want to pull? i.e.
out.object.getString("string") and then passing my parcelable object on to the next activity? I'm just not sure what the content of the method should be in the context of making JSON Objects parcelable.
public class StateParcelable extends JSONObject implements Parcelable {
ArrayList<JSONObject> list;
public StateParcelable(ArrayList<JSONObject> list){
this. list = list;
}
public void writeToParcel(Parcel out, int flags) {
}
public int describeContents(){
return 0;
}
public static final Parcelable.Creator<StateParcelable> CREATOR
= new Parcelable.Creator<StateParcelable>() {
public StateParcelable createFromParcel(Parcel in) {
return new StateParcelable(in);
}
public StateParcelable[] newArray(int size) {
return new StateParcelable[size];
}
};
private StateParcelable(Parcel in) {
}
Since you already have (what appears to be) a JSONArray of JSONObjects, why not just convert it to a String, pass that, and rebuild the JSONArray from that?
Intent intent = new Intent(UnitedStatesActivity.this, StateActivity.class);
intent.putExtra("object", entries.toString());
startActivity(intent);
And then in the following Activity:
JSONArray entries = new JSONArray(getIntent().getStringExtra("object"));
Otherwise, yes, you'd have to loop through and serialize the individual items in your ArrayList and then rebuild and add them to the list.
Also, a side note, you can pass the individual JSONObjects the same way: via toString() and new JSONObject(getIntent().getStringExtra("object")), respectively.

Problem Passing ArrayList from one Activity to Another

i'm continuously running into problems trying to pass an ArrayList from one Activity to another. My code is failing with a Null Pointer Exception when i try to iterate through the ArrayList in my XMLParser Class. I've put print statements into the Activity that generates the ArrayList and it looks fine. Can anyone see what i'm doing wrong or why i get a Null pointer Exception when retrieving the ArrayList?
public void onClick(View v) {
if (selItemArray[0] == null) {
Toast.makeText(getApplicationContext()," Please make a Selection ", Toast.LENGTH_SHORT).show();
} else {
Intent intent = new Intent(Recipes2.this, XMLParser.class);
Log.v("Recipes2", "selItemArray[0] before call to XML Parser : " + selItemArray[0]);
//Log.v("Recipes2", "selItemArray[1] before call to XML Parser : " + selItemArray[1]);
intent.putExtra("items_to_parse", selItemArray);
startActivityForResult(intent, 0);
}
}
public class XMLParser extends ListActivity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Bundle b = getIntent().getExtras();
//itemsToParse = b.getStringArrayList("items_to_parse");
ArrayList<String> itemsToParse = new ArrayList<String>();
itemsToParse = getIntent().getExtras().getStringArrayList("items_to_parse"); Iterator<String> iterator = itemsToParse.iterator(); while(iterator.hasNext())
Log.v("XMLParser", iterator.next().toString());
It looks like you're putting a String array, not a ArrayList<String>.
You used a string array on the "sender" side and are trying to get it back as an ArrayList on the receiver side. That won't work. Use a String array on both sides and -- if necessary -- pull it into an array list.
The procedure for passing the data is here:
Passing String array between two class in android application
To convert it to a List - simply do:
List<String> = Arrays.asList(stringArray);

Help with passing ArrayList and parcelable Activity

So I've been googling most of yesterday and last nite and just can't seem to wrap my head around how to pass an arraylist to a subactivity. There are tons of examples and snippets passing primitive data types, but what I have is an arraylist of type address (address.java below).
I've found a lot of stuff on stackoverflow and around the web on this, but nothing that got a lot of attention except for one with a GeoPoint example. Again, it looked to me like they just flattened the GeoPoint object into two integers and passed it in. I can't do that because my address class may expand to include integers, floats, whatever. Right now, the test app below is only two strings for simplicity. I thought if I could get the parcelalbe stuff working with that, the rest could follow.
Can someone post a working example for an ArrayList of a non-primitive object, or perhaps add code below to make this work?
UPDATE: code below is now working after replies/editing. Thanks!
/* helloParcel.java */
public class helloParcel extends Activity
{
// holds objects of type 'address' == name and state
private ArrayList <address> myList;
#Override
public void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
setContentView (R.layout.main);
Button b1 = (Button) findViewById(R.id.button1);
b1.setOnClickListener(ocl);
myList = new ArrayList();
address frank = new address ("frank", "florida");
address mary = new address ("mary", "maryland");
address monty = new address ("monty", "montana");
myList.add (frank);
myList.add (mary);
myList.add (monty);
// add the myList ArrayList() the the extras for the intent
}
OnClickListener ocl = new OnClickListener()
{
#Override
public void onClick(View v)
{
// fill parceable and launch activity
Intent intent = new Intent().setClass(getBaseContext (), subActivity.class);
// for some reason, I remember a posting saying it's best to create a new
// object to pass. I have no idea why..
ArrayList <address> addyExtras = new ArrayList <address>();
for (int i = 0; i < myList.size(); i++)
addyExtras.add (myList.get(i));
intent.putParcelableArrayListExtra ("mylist", addyExtras);
startActivity(intent);
}
};
}
/* address.java */
public class address implements Parcelable
{
private String name;
private String state;
private static String TAG = "** address **";
public address (String n, String s)
{
name = n;
state = s;
Log.d (TAG, "new address");
}
public address (Parcel in)
{
Log.d (TAG, "parcel in");
name = in.readString ();
state = in.readString ();
}
public String getState ()
{
Log.d (TAG, "getState()");
return (state);
}
public String getName ()
{
Log.d (TAG, "getName()");
return (name);
}
public static final Parcelable.Creator<address> CREATOR
= new Parcelable.Creator<address>()
{
public address createFromParcel(Parcel in)
{
Log.d (TAG, "createFromParcel()");
return new address(in);
}
public address[] newArray (int size)
{
Log.d (TAG, "createFromParcel() newArray ");
return new address[size];
}
};
#Override
public int describeContents ()
{
Log.d (TAG, "describe()");
return 0;
}
#Override
public void writeToParcel (Parcel dest, int flags)
{
Log.d (TAG, "writeToParcel");
dest.writeString (name);
dest.writeString (state);
}
}
/* subActivity.java */
public class subActivity extends Activity
{
private final String TAG = "** subActivity **";
private ArrayList <address> myList;
#Override
protected void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
Log.d (TAG, "onCreate() in subActivity");
setContentView(R.layout.subactivity);
TextView tv1 = (TextView) findViewById(R.id.tv_sub);
myList = getIntent().getParcelableArrayListExtra ("mylist");
Log.d (TAG, "got myList");
for (int i = 0; i < myList.size (); i++)
{
address a = myList.get (i);
Log.d (TAG, "state:" + a.getState ());
tv1.setText (a.getName () + " is from " + a.getState ());
}
}
}
I can see a number of problems here:
Why use addressParcelable? Why not make address implement Parcelable, and then use:
intent.putParcelableArrayListExtra( "addresses", addyExtras );
Your parcelable object must include a static CREATOR. See the documentation for details.
You are not actually adding any extras to the intent before you call startActivity(). See point 1 for a suggestion here.
I think that you will need to address all of these issues in order to get it working.
It can be done MUCH simpler, without all the pain-in-the-ass of implementing Parcelable...
ArrayList (but NOT any List) is Serializable. So, you can put the entire list using putExtra() and retrieve it using getSerializableExtra(), as Sam said.
BUT, I want to add one more important thing: the object your array list stores has to also implement Serializable... and all other complex objects that the object may contain (in your case none) must also implement that (so it's recursive - in order to serialize an object, you must be able to serialize all of its fields).
Now, you might be asking yourself why implementing Serializable instead of Parcelable when there are already methods for reading and writing array lists of parcelables?
Well... the difference is simplicity - just add implements Serializable and optionally private static final long serialVersionUID = SOME_CONSTANT and you're DONE! That is the reason why I never use Parcelable - you can do all those things using Serializable with literally 2 lines of code - instead of many method inheritances and all that stuff...
You can pass Serializable objects via putExtra. ArrayList implements Serializable.
Mike dg is correct!
putExtra() and getSerializable() will store and retrieve an ArrayList<> of your custom objects, with no interface implementing required. Worked for me!

Passing JSONObject into another activity

I'm hitting an external API that's returning JSON data (new dvd titles). I'm able to parse out the JSON and list each dvd title and other dvd information into a ListView just fine. I was also able to setup an onListItemClick method just fine for primitive data (title string). I ended up writing something like so for the onListItemClick method:
Just to note, the productArray is a class var that's being set by another method that holds an array of JSONObjects.
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Intent i = new Intent(DvdListingActivity.this, MovieProductActivity.class);
try {
JSONObject jsonObj = productArray.getJSONObject(position);
i.putExtra("mTitle", jsonObj.getJSONObject("Title").opt("val").toString());
i.putExtra("mRelDate", jsonObj.getJSONObject("RelDate").opt("val").toString());
i.putExtra("mDesc", jsonObj.getJSONObject("Desc").opt("val").toString());
i.putExtra("mRating", jsonObj.getJSONObject("MPAA").getJSONObject("Rating").opt("val").toString());
i.putExtra("mActors", jsonObj.getJSONObject("Actors").opt("val").toString());
i.putExtra("mImage", jsonObj.getJSONObject("Image").opt("val").toString());
startActivity(i);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
The above code all works, but I'm thinking there's GOTTA be a better way for me to pass in data to another Activity. I was thinking that I would be able to pass a JSONObject that contains all the data for a dvd movie instead of setting each data point individually.
I tried for a week and a half to figure out how to use Parcelable. I tried instantiating a new JSONObject jsonObj that implements Parcelable with no luck. I kept getting an error in my LogCat that said that the object was un-parcelable.
I've tried reading the Android developer site and other blogs, but I couldn't apply their examples to what I needed to do.
Any help would be much appreciated
You can simply put an entire JSONObject as a string. Something like this:
i.putString("product", jsonObj.toString);
And then in the MovieProductActivity you could
JSONObject jsonObj = new JSONObject(getIntent().getStringExtra("product"));
From Current Activity
Intent mIntent = new Intent(CurrentActivity.this, TargetActivity.class);
mIntent.putExtra("ITEM_EXTRA", json_object.toString());
startActivity(mIntent);
From Target Activity's onCreate
try {
json_object = new JSONObject(getIntent().getStringExtra("ITEM_EXTRA"));
Log.e(TAG, "Example Item: " + json_object.getString("KEY"));
} catch (JSONException e) {
e.printStackTrace();
}
You can just encapsulate all of the information about a movie into a Movie object, which implements Parcelable.
The code will look similar to above, but instead of passing 6 different extras you can just pass one extra that is the movie.
Movie movie = new Movie();
movie.setTitle(jsonObj.getJSONObject("Title").opt("val").toString());
movie.setRelDat(jsonObj.getJSONObject("RelDate").opt("val").toString());
.
.
.
i.putExtra("movie", movie);
For information on implementing a Parcelable object, see Parcelable docs. You basically just write out each string in 'writeToParcel', and read in each string in 'readFromParcel' in the correct order.
The answer posted here by Cheryl Simon is completely correct however this might not explain it too clearly for someone who is new to android or java. Please let me build on her answer.
The best and cleanest way to do this is with the Parcelable interface.
Declare a class as below that implements Parcelable. This one uses an accessor and mutator which isn't actually necessary.
public class JSonArrayParser implements Parcelable {
private JSONArray jsonArray;
public JSonArrayParser() {}
public JSonArrayParser(JSONArray jsonArray) {
this.jsonArray = jsonArray;
}
public void setJsonArray(JSONArray jsonArray) {
this.jsonArray = jsonArray;
}
public JSONArray getJsonArray() {
return jsonArray;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
}
}
The next step would be to create an instance of that class (in this case JSonArrayParser) and pass it through the Bundle as in the example below.
//This could be an Activity, Fragment, DialogFragment or any class that uses the Bundle Type such as savedInstanceState.
jobItemsPopUp = new JobItemsPopUp();//This example uses a DialogFragment.
Bundle bundle = new Bundle();
JSonArrayParser jSonArrayParser = new JSonArrayParser(jsonArray);
bundle.putParcelable("jsonArray", jSonArrayParser);
jobItemsPopUp.setArguments(bundle);
jobItemsPopUp.show(getActivity().getSupportFragmentManager(), "JobItemsPopUp");
The the way your would retrieve the value would be as follows (I've used the onCreate but you can retrieve the value anywhere):
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
this.jsonArray = ((JSonArrayParser) getArguments().getParcelable("jsonArray")).getJsonArray();
}
}
Just as an additional note: you can do this with custom constructors, but this method has been deprecated by Google.
The old deprecated method (associated with app compat v4 NOT RECOMMENDED) would be:
private Fragment MyFragment;
MyFragment = new MyFragment(jsonArray)
Hope this help and clears a few things up.
The Parcelable interface can be used with any data class with or without mutators and/or accessors.
Are you storing the information in a DB? If you are, you can simply pass the ID of the desired title (via the intent).
Have a look at gson. It allows you to sereialise and deserialise JSON blobs to entire class instances.
Just create a Parcel like this:
public class Parcel implements Serializable {
private JSONObject obj;
public Parcel(JSONObject obj) {
this.obj = obj;
}
public JSONObject getObj() {
return obj;
}}
And put into the bundle:
Parcel p = new Parcel(data);
Bundle args = new Bundle();
args.putSerializable("events", p);
Finally, you can get the JSONObject back using:
JSONObject obj = ((Parcel) ((Bundle) getArguments()).getSerializable("events")).getObj();

Categories

Resources