Issue
Problem
I'm making a simple app that retrieves a data set from a webserver in JSON form, parses it into objects contained in an ArrayList. This array list is then output in the form of a list view. It work's beautifully to start with. Then when I leave the app for a while and come back to it, I get a ANR and have to Force close. However simply leaving the app and returning to it is completely fine. (See full LogCat below) I have included all the relevant code (with my webserver url removed) below. Any help in solving this matter would be appreciated.
I assume it has something to do with the app resuming onResume()
or maybe onRestart()
so I tried overriding both those methods with some isEmpty()
and !null
checks to no avail.
TL;DR: App not responding due to NullPointerException, likely because of some Lifecycle event but can't figure out why.
Another possible mess is my attempt at a singleton class. (Never really used static based instances references before)
LogCat Output of App failing to restart
This LogCat is a read out of the app failing after going to the home screen, opening many other apps, then returning to my app. This does not happen after simply leaving and returning to the app.
05-14 17:21:45.140: W/dalvikvm(22985): threadid=1: thread exiting with uncaught exception (group=0x4103a2a0)
05-14 17:21:45.150: E/AndroidRuntime(22985): FATAL EXCEPTION: main
05-14 17:21:45.150: E/AndroidRuntime(22985): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.liamjpeters.retrievedata/com.liamjpeters.activities.ListActivity}: java.lang.NullPointerException
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2100)
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2125)
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.app.ActivityThread.access$600(ActivityThread.java:140)
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1227)
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.os.Handler.dispatchMessage(Handler.java:99)
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.os.Looper.loop(Looper.java:137)
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.app.ActivityThread.main(ActivityThread.java:4898)
05-14 17:21:45.150: E/AndroidRuntime(22985): at java.lang.reflect.Method.invokeNative(Native Method)
05-14 17:21:45.150: E/AndroidRuntime(22985): at java.lang.reflect.Method.invoke(Method.java:511)
05-14 17:21:45.150: E/AndroidRuntime(22985): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1006)
05-14 17:21:45.150: E/AndroidRuntime(22985): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
05-14 17:21:45.150: E/AndroidRuntime(22985): at dalvik.system.NativeStart.main(Native Method)
05-14 17:21:45.150: E/AndroidRuntime(22985): Caused by: java.lang.NullPointerException
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.widget.ArrayAdapter.getCount(ArrayAdapter.java:330)
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.widget.ListView.setAdapter(ListView.java:466)
05-14 17:21:45.150: E/AndroidRuntime(22985): at com.liamjpeters.activities.ListActivity.onCreate(ListActivity.java:48)
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.app.Activity.performCreate(Activity.java:5206)
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1083)
05-14 17:21:45.150: E/AndroidRuntime(22985): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2064)
05-14 17:21:45.150: E/AndroidRuntime(22985): ... 11 more
Main Activity
The gist of the code (What it's meant to do): Just your bog standard main activity. Has a button that when pressed executes an Async Task that retrieves data from a webserver. When it's finished it launches the ListActivity
activity. Importantly here is where my Singletons instance is set
package com.liamjpeters.activities;
import java.util.ArrayList;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import com.liamjpeters.content.Ticket;
import com.liamjpeters.content.Tickets;
import com.liamjpeters.getData.GetTickets;
import com.liamjpeters.retrievedata.R;
public class MainActivity extends Activity {
Button btn;
ProgressBar pb;
ArrayList<Ticket> tickets = new ArrayList<Ticket>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btnConnect);
pb = (ProgressBar) findViewById(R.id.pbconnect);
pb.setVisibility(View.INVISIBLE);
btn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
getServerData();
pb.setVisibility(View.VISIBLE);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private void getServerData(){
GetTickets gsd = new GetTickets(this);
gsd.execute();
}
public void gotServerData(ArrayList<Ticket> tickets){
pb.setVisibility(View.INVISIBLE);
this.tickets = tickets;
Tickets.setInstance(tickets);
Intent intent = new Intent(this, ListActivity.class);
startActivity(intent);
}
}
GetTickets.java
The gist of the code (What it's meant to do): Gets the data from the server and parses it into objects in an ArrayList called tickets. This works like a charm.
package com.liamjpeters.getData;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.os.AsyncTask;
import android.util.Log;
import com.liamjpeters.activities.MainActivity;
import com.liamjpeters.content.Ticket;
public class GetTickets extends AsyncTask<String, Void, String> {
MainActivity mAct;
ArrayList<Ticket> tickets = new ArrayList<Ticket>();
public GetTickets(Activity activ){
mAct = (MainActivity) activ;
}
protected void onPostExecute(String result) {
mAct.gotServerData(tickets);
}
protected String doInBackground(String... params) {
InputStream is = null;
String result = "";
// Connect to the server and obtain the data from the Server via the PHP Script.
// This script returns the MYSQL results encoded in JSON format
try {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("REMOVED BUT WORKS, TRUST ME");
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
is = entity.getContent();
} catch (Exception e) {
Log.e("log_tag", "Error in http connection " + e.toString());
}
// Converts the returned HTTPEntity into a string that can be parsed
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "iso-8859-1"), 8);
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
is.close();
result = sb.toString();
} catch (Exception e) {
Log.e("log_tag", "Error converting result " + e.toString());
}
// Parses the JSON data
try {
JSONArray jArray = new JSONArray(result);
result = "";
for (int i = 0; i < jArray.length(); i++) {
JSONObject json_data = jArray.getJSONObject(i);
tickets.add(new Ticket(json_data.getInt("ticket_id"),json_data.getString("ticket_name"),json_data.getString("ticket_location"), json_data.getString("ticket_problem_area"),json_data.getString("ticket_description"),json_data.getInt("ticket_priority"),json_data.getInt("ticket_time")));
}
} catch (JSONException e) {
Log.e("log_tag", "Error parsing data " + e.toString());
}
return null;
}
}
Ticket.java
The gist of the code (What it's meant to do): Just a data object with some convenience methods.
package com.liamjpeters.content;
public class Ticket {
private String name;
private String location;
private String problemArea;
private String description;
private int priority;
private int timeInt;
private int id;
private String timeString;
private boolean isNew = false;;
public Ticket(int id,String name, String location, String problemArea, String description, int priority, int time){
this.id = id;
this.name = name;
this.location = location;
this.problemArea = problemArea;
this.description = description;
this.priority = priority;
this.timeInt = time;
if (((System.currentTimeMillis() / 1000L)-time) <84600){
isNew = true;
}
timeString = new java.util.Date((long) time * 1000).toString();
}
public Ticket clone(){
return new Ticket(Integer.valueOf(id), new String(name), new String(location), new String(problemArea), new String(description), Integer.valueOf(priority), Integer.valueOf(timeInt));
}
public int getID(){
return id;
}
public String getName(){
return name;
}
public String getLocation(){
return location;
}
public String getProblemArea(){
return problemArea;
}
public String getDescription(){
return description;
}
public int getPriority(){
return priority;
}
public int getTimeInt(){
return timeInt;
}
public String getTimeString(){
return timeString;
}
public Boolean getIsNew(){
return isNew;
}
}
ListActivity.java
The gist of the code (What it's meant to do): The activity that displays a list view using my custom list item. Likely the lifecycles of this activity that are my problem
package com.liamjpeters.activities;
import java.util.ArrayList;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.widget.ListView;
import com.liamjpeters.content.Ticket;
import com.liamjpeters.content.TicketListAdapter;
import com.liamjpeters.content.Tickets;
import com.liamjpeters.retrievedata.R;
public class ListActivity extends Activity {
ListView lstTest;
TicketListAdapter listAdapter;
ArrayList<Ticket> tickets;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tickets = Tickets.getInstance();
//Ony of my attempts at a fix :S
if (tickets == null || tickets.isEmpty()){
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
setContentView(R.layout.listlayout);
lstTest = (ListView)findViewById(R.id.lstText);
listAdapter = new TicketListAdapter(ListActivity.this,R.layout.listitemrel , tickets);
lstTest.setAdapter(listAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Tickets.java
Kind of a singleton class that helps pass my ArrayList between activities. It originally just stored a reference that it received. I then had a notion that the original ArrayList was created on a thread that must have been disposed of at some point hence the reference would become null. This did not fix the problem
package com.liamjpeters.content;
import java.util.ArrayList;
public class Tickets {
private static ArrayList<Ticket> instance;
public static void setInstance(ArrayList<Ticket> instance){
Tickets.instance = new ArrayList<Ticket>(instance.size());
for (int i = 0; i<instance.size(); i++){
Tickets.instance.add(instance.get(i).clone());
}
}
public static ArrayList<Ticket> getInstance(){
return instance;
}
}
TicketListAdapter
Bog Standard ArrayAdapter. A million examples of these online
package com.liamjpeters.content;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.liamjpeters.retrievedata.R;
public class TicketListAdapter extends ArrayAdapter<Ticket> {
int textViewResourceId;
public TicketListAdapter(Context context, int textViewResourceId, List<Ticket> objects) {
super(context, textViewResourceId, objects);
this.textViewResourceId = textViewResourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//The linear layout that the listview is contained within
LinearLayout ticketView;
//Get the current ticket object
Ticket ticket = getItem(position);
//Inflate the view
if(convertView==null){
ticketView = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater vi;
vi = (LayoutInflater)getContext().getSystemService(inflater);
vi.inflate(textViewResourceId, ticketView, true);
}else{
ticketView = (LinearLayout) convertView;
}
//Get the text boxes from the list item XML file
TextView isNew = (TextView)ticketView.findViewById(R.id.tvIsNew);
TextView idText =(TextView)ticketView.findViewById(R.id.tvTicketID);
TextView nameText =(TextView)ticketView.findViewById(R.id.tvName);
TextView locationText =(TextView)ticketView.findViewById(R.id.tvLocation);
TextView problemText =(TextView)ticketView.findViewById(R.id.tvProblem);
TextView dateText =(TextView)ticketView.findViewById(R.id.tvDate);
ImageView priorityImage = (ImageView)ticketView.findViewById(R.id.ivPriority);
//Assign the appropriate data from our alert object above
if (ticket.getIsNew()){
isNew.setText("NEW!");
}
idText.setText(""+ticket.getID());
nameText.setText("Who: " + ticket.getName());
locationText.setText("Where: "+ ticket.getLocation());
problemText.setText("What: "+ticket.getProblemArea());
dateText.setText(ticket.getTimeString());
switch (ticket.getPriority()){
case 0:
priorityImage.setImageResource(R.drawable.low);
break;
case 1:
priorityImage.setImageResource(R.drawable.med);
break;
case 2:
priorityImage.setImageResource(R.drawable.high);
break;
default:
priorityImage.setImageResource(R.drawable.low);
break;
}
return ticketView;
}
}
So there you have. Any help, big or small is appreciated. See some code that could be done better then please let me know. I'm looking to not only solve my problem but learn as much as possible.
Thanks :)
Solution
The source of the problem is that after some time, the Android framework kills off your application process. When it restarts it, all your static data (including the singleton Tickets
object) are set to default values. You need to detect this situation and reinitialize things to the state you need to function.
One consequence of this is that in your ListActivity
, when you discover that Tickets.getInstance()
returns null
, you should call finish()
* after starting MainActivity
. Otherwise it continues with trying to initialize the list activity.
* and immediately return, as David Wasser points out in his comment.
Answered By - Ted Hopp
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.